Skip to content

Commit

Permalink
Merge pull request #1339 from lugray/chord_plugin
Browse files Browse the repository at this point in the history
Add Chord plugin
  • Loading branch information
obra authored Mar 29, 2023
2 parents e7b3ed5 + 4791bf6 commit 7291137
Show file tree
Hide file tree
Showing 13 changed files with 580 additions and 0 deletions.
43 changes: 43 additions & 0 deletions examples/Keystrokes/Chord/Chord.ino
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();
}
6 changes: 6 additions & 0 deletions examples/Keystrokes/Chord/sketch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:avr:model01",
"port": ""
}
}
1 change: 1 addition & 0 deletions examples/Keystrokes/Chord/sketch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_fqbn: keyboardio:avr:model01
57 changes: 57 additions & 0 deletions plugins/Kaleidoscope-Chord/README.md
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.
7 changes: 7 additions & 0 deletions plugins/Kaleidoscope-Chord/library.properties
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=
21 changes: 21 additions & 0 deletions plugins/Kaleidoscope-Chord/src/Kaleidoscope-Chord.h
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 plugins/Kaleidoscope-Chord/src/kaleidoscope/plugin/Chord.cpp
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;
Loading

0 comments on commit 7291137

Please sign in to comment.