Merge remote-tracking branch 'plugin/OneShot/f/monorepo' into f/monorepo-stage2

pull/389/head
Gergely Nagy 6 years ago
commit cfc2d65bdb
No known key found for this signature in database
GPG Key ID: AC1E90BAC433F68F

@ -0,0 +1,188 @@
# Kaleidoscope-OneShot
One-shots are a new kind of behaviour for your standard modifier and momentary
layer keys: instead of having to hold them while pressing other keys, they can
be tapped and released, and will remain active until any other key is pressed.
In short, they turn `Shift, A` into `Shift+A`, and `Fn, 1` to `Fn+1`. The main
advantage is that this allows us to place the modifiers and layer keys to
positions that would otherwise be awkward when chording. Nevertheless, they
still act as normal when held, that behaviour is not lost.
Furthermore, if a one-shot key is tapped two times in quick succession, it
becomes sticky, and remains active until disabled with a third tap. This can be
useful when one needs to input a number of keys with the modifier or layer
active, and still does not wish to hold the key down. If this feature is
undesirable, unset the `OneShot.double_tap_sticky` property (see later).
To make multi-modifier, or multi-layer shortcuts possible, one-shot keys remain
active if another one-shot of the same type is tapped, so `Ctrl, Alt, b` becomes
`Ctrl+Alt+b`, and `L1, L2, c` is turned into `L1+L2+c`. Furthermore, modifiers
and other layer keys do not cancel the one-shot effect, either.
## Using One-Shot Keys
To enter one-shot mode, tap _quickly_ on a one-shot key. The next
normal (non-one-shot) key you press will have the modifier applied,
and then the modifier will automatically turn off. If the Shift key is
a one-shot modifier, then hitting `Shift, a, b` will give you `Ab`,
_if you hit shift quickly._
Longish keypresses do not activate one-shot mode. If you press `Shift,
a, b`, as above, but hold the Shift key a bit longer, you'll get `ab`.
To enter sticky mode, _tap twice quickly_ on a one-shot key. The
modifier will now stay on until you press it again. Continuing the
`Shift` example, tapping `Shift, Shift` _quickly_ and then `a, b, c,
Shift, d, e, f` will give you `ABCdef`.
This can be a bit tricky; combining this plugin with
[LED-ActiveModColor](LED-ActiveModColor).md
will help you understand what state your one-shot is in; when a
one-shot key is active, it will have a white LED highlight; when
sticky, a red highlight.
## Using the plugin
After adding one-shot keys to the keymap, all one needs to do, is enable the
plugin:
```c++
#include <Kaleidoscope.h>
#include <Kaleidoscope-OneShot.h>
#include <kaleidoscope/hid.h>
// somewhere in the keymap...
OSM(LeftControl), OSL(_FN)
KALEIDOSCOPE_INIT_PLUGINS(OneShot);
void setup() {
Kaleidoscope.setup();
}
```
## Keymap markup
There are two macros the plugin provides:
### `OSM(mod)`
> A macro that takes a single argument, the name of the modifier: `LeftControl`,
> `LeftShift`, `LeftAlt`, `LeftGui` or their right-side variant. When marked up
> with this macro, the modifier will act as a one-shot modifier.
### `OSL(layer)`
> Takes a layer number as argument, and sets up the key to act as a one-shot
> layer key.
>
> Please note that while `Kaleidoscope` supports more, one-shot layers are
> limited to 8 layers only.
## Plugin methods
The plugin provides one object, `OneShot`, which implements both one-shot
modifiers and one-shot layer keys. It has the following methods:
### `.isActive()`
> Returns if any one-shot key is in flight. This makes it possible to
> differentiate between having a modifier or layer active, versus having them
> active only until after the next key getting pressed. And this, in turn, is
> useful for macros that need to fiddle with either modifier or layer state: if
> one-shots are not active, they need not restore the original state.
### `.isPressed()`
> Returns true if any one-shot key is still held.
### `.isSticky(key)`
> Returns if the key is currently sticky.
### `.isModifierActive(key)`
> Returns if the modifier `key` has a one-shot state active. Use this together
> with `hid::isModifierKeyActive` to catch cases where a one-shot modifier is
> active, but not registered yet.
### `.cancel([with_stickies])`
> The `cancel()` method can be used to cancel any pending one-shot effects,
> useful when one changed their minds, and does not wish to wait for the
> timeout.
>
> The optional `with_stickies` argument, if set to `true`, will also cancel
> sticky one-shot effects. If omitted, it defaults to `false`, and not canceling
> stickies.
### `.inject(key, keyState)`
> Simulates a key event, specifically designed to inject one-shot keys into the
> event loop. The primary purpose of this method is to make it easier to trigger
> multiple one-shots at the same time.
>
> See the example sketch for more information about its use.
## Plugin properties
Along with the methods listed above, the `OneShot` object has the following
properties too:
### `.time_out`
> Set this property to the number of milliseconds to wait before timing out and
> cancelling the one-shot effect (unless interrupted or cancelled before by any
> other means).
>
> Defaults to 2500.
### `.hold_time_out`
> Set this property to the number of milliseconds to wait before considering a
> held one-shot key as intentionally held. In this case, the one-shot effect
> will not trigger when the key is released. In other words, holding a one-shot
> key at least this long, and then releasing it, will not trigger the one-shot
> effect.
>
> Defaults to 200.
### `.double_tap_sticky`
> Set this boolean property to make the plugin treat a double-tap of a one-shot
> key as making it sticky until a third tap. Setting it to `false` disables this
> behaviour, in which case double-tapping a one-shot modifier will just restart
> the timer.
>
> Defaults to `true`.
### `.double_tap_sticky_layers`
> The same as `.double_tap_sticky`, but only applies to layers. The two can be
> set separately.
>
> Defaults to `true`.
### `.double_tap_time_out`
> Set this property to the number of milliseconds within which a second
> uninterrupted tap of the same one-shot key will be treated as a sticky-tap.
> Only takes effect when `.double_tap_sticky` is set.
>
>
> Setting the property to `-1` will make the double-tap timeout use `.time_out`
> for its calculations.
>
> Defaults to -1.
## Dependencies
* [Kaleidoscope-Ranges](Ranges.md)
## Further reading
Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin.
[plugin:example]: ../../examples/OneShot/OneShot.ino

@ -0,0 +1,88 @@
/* -*- mode: c++ -*-
* Kaleidoscope-OneShot -- One-shot modifiers and layers
* Copyright (C) 2016-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-Macros.h>
#include <Kaleidoscope-OneShot.h>
// Macros
enum {
OSMALTCTRL,
};
// *INDENT-OFF*
const Key keymaps[][ROWS][COLS] PROGMEM = {
[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,
OSM(LeftControl), Key_Backspace, OSM(LeftGui), OSM(LeftShift),
M(OSMALTCTRL),
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,
OSM(RightShift), OSM(RightAlt), Key_Spacebar, OSM(RightControl),
OSL(1)),
[1] = KEYMAP_STACKED
(
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
Key_UpArrow, Key_DownArrow, Key_LeftArrow, Key_RightArrow,___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___),
};
// *INDENT-ON*
void macroOneShotAltControl(uint8_t keyState) {
OneShot.inject(OSM(LeftAlt), keyState);
OneShot.inject(OSM(LeftControl), keyState);
}
const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) {
if (macroIndex == OSMALTCTRL) {
macroOneShotAltControl(keyState);
}
return MACRO_NONE;
}
KALEIDOSCOPE_INIT_PLUGINS(OneShot, Macros);
void setup() {
Kaleidoscope.setup();
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,20 @@
/* -*- mode: c++ -*-
* Kaleidoscope-OneShot -- One-shot modifiers and layers
* Copyright (C) 2016-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/>.
*/
#pragma once
#include <kaleidoscope/plugin/OneShot.h>

@ -0,0 +1,274 @@
/* -*- mode: c++ -*-
* Kaleidoscope-OneShot -- One-shot modifiers and layers
* Copyright (C) 2016-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-OneShot.h>
namespace kaleidoscope {
namespace plugin {
// ---- state ---------
uint32_t OneShot::start_time_ = 0;
uint16_t OneShot::time_out = 2500;
uint16_t OneShot::hold_time_out = 250;
int16_t OneShot::double_tap_time_out = -1;
bool OneShot::double_tap_sticky = true;
bool OneShot::double_tap_sticky_layers = true;
OneShot::state_t OneShot::state_;
OneShot::state_t OneShot::sticky_state_;
OneShot::state_t OneShot::pressed_state_;
Key OneShot::prev_key_;
bool OneShot::should_cancel_ = false;
bool OneShot::should_cancel_stickies_ = false;
bool OneShot::should_mask_on_interrupt_ = false;
uint8_t OneShot::positions_[16];
// --- helper macros ------
#define isOneShotKey(key) (key.raw >= ranges::OS_FIRST && key.raw <= ranges::OS_LAST)
#define isModifier(key) (key.raw >= Key_LeftControl.raw && key.raw <= Key_RightGui.raw)
#define isLayerKey(key) (key.flags == (KEY_FLAGS | SYNTHETIC | SWITCH_TO_KEYMAP) && key.keyCode >= LAYER_SHIFT_OFFSET && key.keyCode <= LAYER_SHIFT_OFFSET + 23)
#define isOneShot(idx) (bitRead (state_.all, (idx)))
#define setOneShot(idx) (bitWrite (state_.all, idx, 1))
#define clearOneShot(idx) (bitWrite (state_.all, idx, 0))
#define isSticky_(idx) (bitRead (sticky_state_.all, idx))
#define setSticky(idx) (bitWrite (sticky_state_.all, idx, 1))
#define clearSticky(idx) bitWrite (sticky_state_.all, idx, 0)
#define setPressed(idx) bitWrite(pressed_state_.all, idx, 1)
#define clearPressed(idx) bitWrite(pressed_state_.all, idx, 0)
#define isPressed(idx) bitRead (pressed_state_.all, idx)
#define isSameAsPrevious(key) (key.raw == prev_key_.raw)
#define saveAsPrevious(key) prev_key_.raw = key.raw
#define hasTimedOut() (millis () - start_time_ >= time_out)
void OneShot::positionToCoords(uint8_t pos, byte *row, byte *col) {
*col = pos % COLS;
*row = (pos - *col) / COLS;
}
// ---- OneShot stuff ----
void OneShot::injectNormalKey(uint8_t idx, uint8_t key_state) {
Key key;
byte row, col;
if (idx < 8) {
key.flags = Key_LeftControl.flags;
key.keyCode = Key_LeftControl.keyCode + idx;
} else {
key.flags = KEY_FLAGS | SYNTHETIC | SWITCH_TO_KEYMAP;
key.keyCode = LAYER_SHIFT_OFFSET + idx - 8;
}
positionToCoords(positions_[idx], &row, &col);
handleKeyswitchEvent(key, row, col, key_state | INJECTED);
}
void OneShot::activateOneShot(uint8_t idx) {
injectNormalKey(idx, IS_PRESSED);
}
void OneShot::cancelOneShot(uint8_t idx) {
clearOneShot(idx);
injectNormalKey(idx, WAS_PRESSED);
}
EventHandlerResult OneShot::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t keyState) {
uint8_t idx = mapped_key.raw - ranges::OS_FIRST;
if (keyState & INJECTED)
return EventHandlerResult::OK;
if (!state_.all) {
if (!isOneShotKey(mapped_key)) {
return EventHandlerResult::OK;
}
if (keyToggledOff(keyState)) {
clearPressed(idx);
if (mapped_key >= ranges::OSL_FIRST && mapped_key <= ranges::OSL_LAST) {
should_mask_on_interrupt_ = true;
}
} else if (keyToggledOn(keyState)) {
start_time_ = millis();
positions_[idx] = row * COLS + col;
setPressed(idx);
setOneShot(idx);
saveAsPrevious(mapped_key);
activateOneShot(idx);
}
return EventHandlerResult::EVENT_CONSUMED;
}
if (!keyIsPressed(keyState) && !keyWasPressed(keyState))
return EventHandlerResult::OK;
if (isOneShotKey(mapped_key)) {
if (isSticky_(idx)) {
if (keyToggledOn(keyState)) { // maybe on _off instead?
saveAsPrevious(mapped_key);
clearSticky(idx);
cancelOneShot(idx);
should_cancel_ = false;
}
} else {
if (keyToggledOff(keyState)) {
clearPressed(idx);
if ((millis() - start_time_) >= hold_time_out) {
cancelOneShot(idx);
should_cancel_ = false;
}
}
if (keyToggledOn(keyState)) {
setPressed(idx);
bool set_sticky = false;
if (isSameAsPrevious(mapped_key)) {
if (mapped_key >= ranges::OSM_FIRST && mapped_key <= ranges::OSM_LAST && double_tap_sticky)
set_sticky = true;
else if (mapped_key >= ranges::OSL_FIRST && mapped_key <= ranges::OSL_LAST && double_tap_sticky_layers)
set_sticky = true;
}
if (set_sticky) {
if ((millis() - start_time_) <= ((double_tap_time_out == -1) ? time_out : double_tap_time_out)) {
setSticky(idx);
saveAsPrevious(mapped_key);
}
} else {
start_time_ = millis();
positions_[idx] = row * COLS + col;
setOneShot(idx);
saveAsPrevious(mapped_key);
activateOneShot(idx);
}
}
}
return EventHandlerResult::EVENT_CONSUMED;
}
// ordinary key here, with some event
if (keyIsPressed(keyState)) {
saveAsPrevious(mapped_key);
if (!isModifier(mapped_key) && (mapped_key.flags != (KEY_FLAGS | SYNTHETIC | SWITCH_TO_KEYMAP))) {
if (should_mask_on_interrupt_)
KeyboardHardware.maskKey(row, col);
should_cancel_ = true;
}
}
return EventHandlerResult::OK;
}
EventHandlerResult OneShot::beforeReportingState() {
if (!state_.all)
return EventHandlerResult::OK;
for (uint8_t i = 0; i < 8; i++) {
if (isOneShot(i)) {
activateOneShot(i);
}
}
return EventHandlerResult::OK;
}
EventHandlerResult OneShot::afterEachCycle() {
if (!state_.all)
return EventHandlerResult::OK;
if (hasTimedOut())
cancel();
bool is_cancelled = false;
for (uint8_t i = 0; i < 32; i++) {
if (should_cancel_) {
if (isSticky_(i)) {
if (should_cancel_stickies_) {
is_cancelled = true;
clearSticky(i);
cancelOneShot(i);
clearPressed(i);
}
} else if (isOneShot(i) && !isPressed(i)) {
is_cancelled = true;
cancelOneShot(i);
}
}
}
if (is_cancelled) {
should_cancel_ = false;
should_cancel_stickies_ = false;
should_mask_on_interrupt_ = false;
}
return EventHandlerResult::OK;
}
void OneShot::inject(Key mapped_key, uint8_t key_state) {
onKeyswitchEvent(mapped_key, UNKNOWN_KEYSWITCH_LOCATION, key_state);
}
// --- glue code ---
bool OneShot::isActive(void) {
return (state_.all && !hasTimedOut()) || (pressed_state_.all) || (sticky_state_.all);
}
bool OneShot::isActive(Key key) {
uint8_t idx = key.raw - ranges::OS_FIRST;
return (bitRead(state_.all, idx) && !hasTimedOut()) || (isPressed(idx)) || (isSticky_(idx));
}
bool OneShot::isSticky(Key key) {
uint8_t idx = key.raw - ranges::OS_FIRST;
return (isSticky_(idx));
}
bool OneShot::isModifierActive(Key key) {
if (key.raw < Key_LeftControl.raw || key.raw > Key_RightGui.raw)
return false;
return isOneShot(key.keyCode - Key_LeftControl.keyCode);
}
void OneShot::cancel(bool with_stickies) {
should_cancel_ = true;
should_cancel_stickies_ = with_stickies;
}
}
}
kaleidoscope::plugin::OneShot OneShot;

@ -0,0 +1,87 @@
/* -*- mode: c++ -*-
* Kaleidoscope-OneShot -- One-shot modifiers and layers
* Copyright (C) 2016-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/>.
*/
#pragma once
#include <Kaleidoscope.h>
#include <Kaleidoscope-Ranges.h>
#define OSM(kc) (Key) {.raw = kaleidoscope::ranges::OSM_FIRST + (Key_ ## kc).keyCode - Key_LeftControl.keyCode}
#define OSL(n) (Key) {.raw = kaleidoscope::ranges::OSL_FIRST + n}
namespace kaleidoscope {
namespace plugin {
class OneShot : public kaleidoscope::Plugin {
public:
OneShot(void) {}
static bool isOneShotKey(Key key) {
return (key.raw >= kaleidoscope::ranges::OS_FIRST && key.raw <= kaleidoscope::ranges::OS_LAST);
}
static bool isActive(void);
static bool isPressed() {
return !!pressed_state_.all;
}
static bool isActive(Key key);
static bool isSticky(Key key);
static void cancel(bool with_stickies);
static void cancel(void) {
cancel(false);
}
static uint16_t time_out;
static int16_t double_tap_time_out;
static uint16_t hold_time_out;
static bool double_tap_sticky;
static bool double_tap_sticky_layers;
static bool isModifierActive(Key key);
EventHandlerResult beforeReportingState();
EventHandlerResult afterEachCycle();
EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t keyState);
void inject(Key mapped_key, uint8_t key_state);
private:
typedef union {
struct {
uint8_t mods;
uint8_t layers;
};
uint16_t all;
} state_t;
static uint32_t start_time_;
static state_t state_;
static state_t sticky_state_;
static state_t pressed_state_;
static Key prev_key_;
static bool should_cancel_;
static bool should_cancel_stickies_;
static bool should_mask_on_interrupt_;
static uint8_t positions_[16];
static void positionToCoords(uint8_t pos, byte *row, byte *col);
static void injectNormalKey(uint8_t idx, uint8_t key_state);
static void activateOneShot(uint8_t idx);
static void cancelOneShot(uint8_t idx);
};
}
}
extern kaleidoscope::plugin::OneShot OneShot;
Loading…
Cancel
Save