Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion docs/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ automatically become a OneShot key when pressed, by applying modifier flags to
#### Two new special OneShot keys

OneShot can now also turn _any_ key into a sticky key, using either of two
special `Key` values that can be inserted in the keymap.
special `Key` values that can be inserted in the keymap. Configure the
`OneShotMetaKeys` plugin to use these.

##### `OneShot_MetaStickyKey`

Expand All @@ -151,6 +152,14 @@ pressed. Press `X`, press `OneShot_ActiveStickyKey`, and release `X`, and `X`
will be sticky until it is pressed again to deactivate it. Again, it works on
any key value, so use with caution.

Additionally, when you add the `EscapeOneShot` plugin, this key will act as a
toggle when you press it again while holding no other keys.

##### `Key_OneShotCancel`

When you configure the `EscapeOneShot` plugin, this key cancels all OneShot
keys.

#### LED-ActiveModColor highlighting

With the updates to OneShot, LED-ActiveModColor now recognizes and highlights
Expand Down
7 changes: 6 additions & 1 deletion plugins/Kaleidoscope-Escape-OneShot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ effect - or act as the normal `Esc` key if none are active, or if any of them
are still held. For those times when one accidentally presses a one-shot key, or
change their minds.

Additionally, the special `Key_OneShotCancel` key will also count as a oneshot
This plugin also modifies the behavior of `OneShot_ActiveStickyKey`
so that it acts like a toggle. Without holding any keys, pressing
`OneShot_ActiveStickyKey` again will cancel any active sticky keys.
This saves one from having to remember to deactivate each key individually.

Additionally, the special `Key_OneShotCancel` key will always count as a oneshot
cancel key, would one want a dedicated key for the purpose.

## Using the plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@

#include "kaleidoscope/plugin/Escape-OneShot.h"

#include <Kaleidoscope-OneShot.h> // for OneShot
#include <Kaleidoscope-OneShot.h> // for OneShot
#include <Kaleidoscope-OneShotMetaKeys.h> // for OneShot_ActiveStickyKey

#include "kaleidoscope/KeyEvent.h" // for KeyEvent
#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult, EventHandlerResult::E...
Expand All @@ -32,19 +33,42 @@
namespace kaleidoscope {
namespace plugin {

EventHandlerResult EscapeOneShot::onKeyswitchEvent(KeyEvent &event) {
if (!event.addr.isValid() || keyIsInjected(event.state))
return EventHandlerResult::OK;
// Track physically-held keys - for toggling OneShot_ActiveStickyKey below.
// Needed because OneShot::onKeyEvent blocks release events.
if (keyToggledOn(event.state)) {
held_addrs_.set(event.addr);
} else if (keyToggledOff(event.state)) {
held_addrs_.clear(event.addr);
}
return EventHandlerResult::OK;
}

EventHandlerResult EscapeOneShot::onKeyEvent(KeyEvent &event) {
// We only act on an escape key (or `cancel_oneshot_key_`, if that has been
// set) that has just been pressed, and not generated by some other
// plugin. Also, only if at least one OneShot key is active and/or
// sticky. Last, only if there are no OneShot keys currently being held.
// sticky.
//
// `Key_OneShotCancel` will always count as an escape key, even if not
// explicitly set so.
// Two keys will always count as an escape key, even if not explicitly set:
// - `Key_OneShotCancel` - explicit escape key
// - `OneShot_ActiveStickyKey` - toggle off if no other keys are held
if ((event.key == settings_.cancel_oneshot_key ||
event.key == Key_OneShotCancel) &&
event.key == Key_OneShotCancel ||
event.key == OneShot_ActiveStickyKey) &&
keyToggledOn(event.state) &&
!keyIsInjected(event.state) &&
::OneShot.isActive()) {
if (event.key == OneShot_ActiveStickyKey) {
for (KeyAddr held_addr : held_addrs_) {
if (Runtime.lookupKey(held_addr) != OneShot_ActiveStickyKey) {
// Keys are still physically held - don't cancel.
return EventHandlerResult::OK;
}
}
}
// Cancel all OneShot keys
::OneShot.cancel(true);
// Change the cancellation key to a blank key, and signal that event
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <Kaleidoscope-Ranges.h> // for OS_CANCEL
#include <stdint.h> // for uint16_t

#include "kaleidoscope/KeyAddrBitfield.h" // for KeyAddrBitfield
#include "kaleidoscope/KeyEvent.h" // for KeyEvent
#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult
#include "kaleidoscope/key_defs.h" // for Key, Key_Escape
Expand All @@ -40,6 +41,7 @@ namespace plugin {

class EscapeOneShot : public kaleidoscope::Plugin {
public:
EventHandlerResult onKeyswitchEvent(KeyEvent &event);
EventHandlerResult onKeyEvent(KeyEvent &event);

void setCancelKey(Key cancel_key) {
Expand All @@ -56,6 +58,7 @@ class EscapeOneShot : public kaleidoscope::Plugin {
Key cancel_oneshot_key;
};
Settings settings_ = {.cancel_oneshot_key = Key_Escape};
KeyAddrBitfield held_addrs_;
};

class EscapeOneShotConfig : public Plugin {
Expand Down
7 changes: 6 additions & 1 deletion plugins/Kaleidoscope-OneShotMetaKeys/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ to make any key on the keyboard (not just modifiers and layer shift keys)
These are both `Key` values that can be used as entries in your sketch's keymap.

Any keys made sticky in this way can be released just like OneShot modifier
keys, by tapping them again to cancel the effect.
keys, by tapping them again to cancel the effect. You can also use the
`EscapeOneShot` plugin to cancel sticky keys without having to remember them.

## The `OneShot_MetaStickyKey`

Expand All @@ -25,6 +26,10 @@ currently held (or otherwise active) sticky. Press (and hold) `X`, tap
`OneShot_ActiveStickyKey`, then release `X`, and `X` will stay active until it
is tapped again to deactivate it.

Additionally, if you add the `EscapeOneShot` plugin, then it acts as a toggle
when no other keys are held. This saves having to remember which keys you've
made sticky.

## Using the plugin

To use the plugin, just include one of the two special OneShot keys somewhere in
Expand Down
2 changes: 1 addition & 1 deletion tests/plugins/Escape-OneShot/basic/test.ktest
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ VERSION 1

KEYSWITCH OSM_0 0 0 # left shift
KEYSWITCH OSM_1 0 1 # left alt
KEYSWITCH ESC 1 0
KEYSWITCH ESC 1 0 # escape

# ==============================================================================
NAME EscapeOneShot cancel temporary
Expand Down
27 changes: 27 additions & 0 deletions tests/plugins/Escape-OneShot/meta-keys/common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// -*- mode: c++ -*-

/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2025 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 {

} // namespace testing
} // namespace kaleidoscope
56 changes: 56 additions & 0 deletions tests/plugins/Escape-OneShot/meta-keys/meta-keys.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* -*- mode: c++ -*-
* Copyright (C) 2025 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-OneShotMetaKeys.h>
#include <Kaleidoscope-Escape-OneShot.h>

#include "./common.h"

// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
Key_A, Key_B, ___, ___, ___, ___, ___,
OneShot_ActiveStickyKey, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,

___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
)
// *INDENT-ON*

KALEIDOSCOPE_INIT_PLUGINS(OneShot, OneShotMetaKeys, EscapeOneShot);

void setup() {
Kaleidoscope.setup();
OneShot.setTimeout(50);
OneShot.setHoldTimeout(20);
OneShot.setDoubleTapTimeout(20);
}

void loop() {
Kaleidoscope.loop();
}
6 changes: 6 additions & 0 deletions tests/plugins/Escape-OneShot/meta-keys/sketch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:virtual:model01",
"port": ""
}
}
1 change: 1 addition & 0 deletions tests/plugins/Escape-OneShot/meta-keys/sketch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_fqbn: keyboardio:virtual:model01
34 changes: 34 additions & 0 deletions tests/plugins/Escape-OneShot/meta-keys/test.ktest
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
VERSION 1

KEYSWITCH KEY_A 0 0
KEYSWITCH KEY_B 0 1
KEYSWITCH OS_ASK 1 0 # OneShot_ActiveStickyKey

# ==============================================================================
NAME EscapeOneShot cancel ActiveStickyKey

RUN 4 ms
PRESS KEY_A
RUN 1 cycle
EXPECT keyboard-report Key_A
RUN 4 ms
PRESS KEY_B
RUN 1 cycle
EXPECT keyboard-report Key_A Key_B
RUN 4 ms
PRESS OS_ASK
RUN 4 ms
RELEASE KEY_A
RUN 4 ms
RELEASE KEY_B
RUN 4 ms
RELEASE OS_ASK
RUN 4 ms

PRESS OS_ASK
RUN 1 cycle
EXPECT keyboard-report Key_B # Key_A gets cancelled first
EXPECT keyboard-report empty # Report should now be empty
RELEASE OS_ASK

RUN 5 ms