-
Notifications
You must be signed in to change notification settings - Fork 259
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1339 from lugray/chord_plugin
Add Chord plugin
- Loading branch information
Showing
13 changed files
with
580 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// -*- mode: c++ -*- | ||
|
||
#include <Kaleidoscope.h> | ||
#include "Kaleidoscope-TopsyTurvy.h" | ||
#include <Kaleidoscope-Chord.h> | ||
|
||
// clang-format 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, | ||
Key_NoKey, | ||
|
||
Key_NoKey, 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, | ||
Key_NoKey | ||
), | ||
) | ||
|
||
KALEIDOSCOPE_INIT_PLUGINS(TopsyTurvy, Chord); | ||
|
||
void setup() { | ||
CHORDS( | ||
CHORD(Key_J, Key_K), Key_Escape, | ||
CHORD(Key_D, Key_F), Key_LeftShift, | ||
CHORD(Key_S, Key_D), TOPSY(Semicolon), | ||
CHORD(Key_S, Key_D, Key_F), Key_Spacebar, | ||
) | ||
Kaleidoscope.setup(); | ||
} | ||
|
||
void loop() { | ||
Kaleidoscope.loop(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"cpu": { | ||
"fqbn": "keyboardio:avr:model01", | ||
"port": "" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
default_fqbn: keyboardio:avr:model01 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# Chord | ||
|
||
## Concept | ||
|
||
This Kaleidoscope plugin allows you to define a chord of keys on your keyboard | ||
which, when pressed simultaneously, produce a single keycode. This differs from | ||
[MagicCombo](https://github.com/keyboardio/Kaleidoscope/tree/master/plugins/Kaleidoscope-MagicCombo) | ||
in that the individual keys making up a chord are suppressed, producing only | ||
the singular result. | ||
|
||
|
||
## Setup | ||
|
||
- Include the header file: | ||
``` | ||
#include <Kaleidoscope-Chord.h> | ||
``` | ||
- Use the plugin in the `KALEIDOSCOPE_INIT_PLUGINS` macro: | ||
``` | ||
KALEIDOSCOPE_INIT_PLUGINS(Chord); | ||
``` | ||
|
||
And define some chords in `setup` such as: | ||
|
||
``` | ||
CHORDS( | ||
CHORD(Key_J, Key_K), Key_Escape, | ||
CHORD(Key_D, Key_F), Key_LeftShift, | ||
CHORD(Key_S, Key_D), TOPSY(Semicolon), | ||
CHORD(Key_S, Key_D, Key_F), Key_Spacebar, | ||
) | ||
``` | ||
|
||
As can be seen from the example, chords can be overlapping or subsets of each | ||
other, and can result in regular keys, modifier keys or special keys (such as a | ||
[TopsyTurvey](https://github.com/keyboardio/Kaleidoscope/tree/master/plugins/Kaleidoscope-TopsyTurvy) | ||
key). The resulting key will be held for as long as the last key pressed in the | ||
chord is held. | ||
|
||
## Configuration | ||
|
||
### `.setTimeout(timeout)` | ||
|
||
> Sets the time (in milliseconds) after which a key or set of keys that could be | ||
> part of a larger chord is pressed before the pressed keys are resolved. It's | ||
> generally not necessary to explicitly wait for this timeout, since as soon as | ||
> a key is pressed that could not be part of a chord with existing key presses, | ||
> the existing keys will resolve. For instance, with the example above, pressing | ||
> and holding S, D, L in quick succession would result in a held Shift + L. It's | ||
> only if you wanted to type Shift + F, that you'd need to add a pause (S, D, | ||
> wait for timeout, F), since otherwise it would be interpreted as a space. | ||
> | ||
> Defaults to `50`. | ||
## Further reading | ||
|
||
The [example](/examples/Keystrokes/Chord/Chord.ino) can help to learn how to use this plugin. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
name=Kaleidoscope-Chord | ||
version=0.0.0 | ||
sentence=Respond to chords of keys as a single keystroke | ||
maintainer=Kaleidoscope's Developers <[email protected]> | ||
url=https://github.com/keyboardio/Kaleidoscope | ||
author=Lisa Ugray | ||
paragraph= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* -*- mode: c++ -*- | ||
* Kaleidoscope-Chord -- Respond to chords of keys as a single keystroke | ||
* Copyright (C) 2023 Lisa Ugray | ||
* | ||
* 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, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* 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/Chord.h" // IWYU pragma: export |
189 changes: 189 additions & 0 deletions
189
plugins/Kaleidoscope-Chord/src/kaleidoscope/plugin/Chord.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
/* -*- mode: c++ -*- | ||
* Kaleidoscope-Chord -- Respond to chords of keys as a single keystroke | ||
* Copyright (C) 2023 Lisa Ugray | ||
* | ||
* 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, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* 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/Chord.h" | ||
|
||
#include <Arduino.h> // for PROGMEM | ||
#include <stdint.h> // for uint8_t, uint16_t, int8_t | ||
|
||
#include "kaleidoscope/KeyAddr.h" // for KeyAddr | ||
#include "kaleidoscope/KeyEvent.h" // for KeyEvent | ||
#include "kaleidoscope/KeyEventTracker.h" // for KeyEventTracker | ||
#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult | ||
#include "kaleidoscope/plugin.h" // for Plugin | ||
#include "kaleidoscope/key_defs.h" // for Key, Key_Transparent | ||
#include "kaleidoscope/progmem_helpers.h" // for cloneFromProgmem | ||
#include "kaleidoscope/keyswitch_state.h" // for keyToggledOn | ||
#include "kaleidoscope/Runtime.h" // for Runtime | ||
|
||
namespace kaleidoscope { | ||
namespace plugin { | ||
EventHandlerResult Chord::onKeyswitchEvent(KeyEvent &event) { | ||
if (event_tracker_.shouldIgnore(event)) { | ||
return EventHandlerResult::OK; | ||
} | ||
|
||
if (!keyToggledOn(event.state)) { | ||
resolveOrArpeggiate(); | ||
return EventHandlerResult::OK; | ||
} | ||
|
||
appendEvent(event); | ||
|
||
if (isChordStrictSubset()) { | ||
start_time_ = Runtime.millisAtCycleStart(); | ||
return EventHandlerResult::ABORT; | ||
} | ||
|
||
Key target_key = getChord(); | ||
|
||
if (target_key == Key_NoKey) { | ||
potential_chord_size_--; | ||
resolveOrArpeggiate(); | ||
return EventHandlerResult::OK; | ||
} | ||
|
||
potential_chord_size_ = 0; | ||
event.key = target_key; | ||
return EventHandlerResult::OK; | ||
} | ||
|
||
EventHandlerResult Chord::afterEachCycle() { | ||
if (Runtime.hasTimeExpired(start_time_, timeout_) && potential_chord_size_ > 0) { | ||
resolveOrArpeggiate(); | ||
} | ||
return EventHandlerResult::OK; | ||
} | ||
|
||
void Chord::setTimeout(uint8_t timeout) { | ||
timeout_ = timeout; | ||
} | ||
|
||
void Chord::resolveOrArpeggiate() { | ||
Key target_key = getChord(); | ||
if (target_key == Key_NoKey) { | ||
arpeggiate(); | ||
} else { | ||
resolve(target_key); | ||
} | ||
} | ||
|
||
void Chord::resolve(Key target_key) { | ||
KeyEvent event = potential_chord_[potential_chord_size_ - 1]; | ||
potential_chord_size_ = 0; | ||
|
||
KeyEventId stored_id = event.id(); | ||
KeyEvent restored_event = KeyEvent(event.addr, event.state, target_key, stored_id); | ||
Runtime.handleKeyEvent(restored_event); | ||
} | ||
|
||
bool Chord::inChord(uint8_t index, Key key) { | ||
for (uint8_t i = index; index < chord_defs_size_ - 1; i++) { | ||
Key chord_key = cloneFromProgmem(chord_defs_[i]); | ||
if (chord_key == Key_NoKey) { | ||
return false; | ||
} | ||
if (chord_key == key) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
bool Chord::isChordStrictSubset() { | ||
uint8_t c = 0; | ||
while (c < chord_defs_size_) { | ||
uint8_t cs = chordSize(c); | ||
if (isChordStrictSubsetOf(c, cs)) { | ||
return true; | ||
} | ||
c += cs + 2; | ||
} | ||
return false; | ||
} | ||
|
||
bool Chord::isChordStrictSubsetOf(uint8_t c, uint8_t cs) { | ||
if (cs <= potential_chord_size_) { | ||
return false; | ||
} | ||
for (uint8_t i = 0; i < potential_chord_size_; i++) { | ||
if (!inChord(c, potential_chord_[i].key)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
uint8_t Chord::chordSize(uint8_t index) { | ||
uint8_t size = 0; | ||
uint8_t i = index; | ||
while (i < chord_defs_size_ - 1) { | ||
Key key = cloneFromProgmem(chord_defs_[i]); | ||
if (key == Key_NoKey) { | ||
break; | ||
} | ||
size++; | ||
i++; | ||
} | ||
return size; | ||
} | ||
|
||
bool Chord::isChord(uint8_t c, uint8_t cs) { | ||
if (cs != potential_chord_size_) { | ||
return false; | ||
} | ||
for (uint8_t i = 0; i < potential_chord_size_; i++) { | ||
if (!inChord(c, potential_chord_[i].key)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
Key Chord::getChord() { | ||
uint8_t c = 0; | ||
while (c < chord_defs_size_) { | ||
uint8_t cs = chordSize(c); | ||
if (isChord(c, cs)) { | ||
return cloneFromProgmem(chord_defs_[c + cs + 1]); | ||
} | ||
c += cs + 2; | ||
} | ||
return Key_NoKey; | ||
} | ||
|
||
void Chord::appendEvent(KeyEvent event) { | ||
if (potential_chord_size_ < kMaxChordSize) { | ||
potential_chord_[potential_chord_size_] = event; | ||
potential_chord_size_++; | ||
} | ||
} | ||
|
||
void Chord::arpeggiate() { | ||
for (uint8_t i = 0; i < potential_chord_size_; i++) { | ||
KeyEvent event = potential_chord_[i]; | ||
KeyEventId stored_id = event.id(); | ||
KeyEvent restored_event = KeyEvent(event.addr, event.state, event.key, stored_id); | ||
Runtime.handleKeyEvent(restored_event); | ||
} | ||
potential_chord_size_ = 0; | ||
} | ||
} // namespace plugin | ||
} // namespace kaleidoscope | ||
|
||
kaleidoscope::plugin::Chord Chord; |
Oops, something went wrong.