diff --git a/examples/Keystrokes/AutoShift/AutoShift.ino b/examples/Keystrokes/AutoShift/AutoShift.ino index 4b6cbc353e..bb9c11bcf0 100644 --- a/examples/Keystrokes/AutoShift/AutoShift.ino +++ b/examples/Keystrokes/AutoShift/AutoShift.ino @@ -65,6 +65,9 @@ void setup() { AutoShift.setEnabled(AutoShift.letterKeys() | AutoShift.numberKeys()); // Add symbol keys to the enabled categories: AutoShift.enable(AutoShift.symbolKeys()); + // instead of shifting, produce a backslash on long pressing slash + AUTOSHIFT( + kaleidoscope::plugin::LongPress(Key_Slash, Key_Backslash), ) // Set the AutoShift long-press time to 150ms: AutoShift.setTimeout(150); // Start with AutoShift turned off: diff --git a/plugins/Kaleidoscope-AutoShift/README.md b/plugins/Kaleidoscope-AutoShift/README.md index f16b72f781..1987c44fd6 100644 --- a/plugins/Kaleidoscope-AutoShift/README.md +++ b/plugins/Kaleidoscope-AutoShift/README.md @@ -78,6 +78,30 @@ 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). +## Producing other characters than shifted variants of the keys + +It is possible to produce other characters than just shifted variants of the +pressed key by providing an explicit mapping between the pressed key and the +key that should be produced instead. + +Such a mapping must be defined in the `setup` method in your sketch: + +``` +AUTOSHIFT( + kaleidoscope::plugin::LongPress(Key_Slash, Key_Backslash), + kaleidoscope::plugin::LongPress(Key_Z, ShiftToLayer(SYMBOL)), +) +``` + +Such explicit mappings take precedence over shifting the key. That +means if all alphanumeric characters are configured for AutoShift, but +the ‘e’ key has an explicit mapping to produce ‘ë’, a long press on ‘e’ +will result in ’ë’, not ‘E’. + +As can be seen in the example above the resulting key does not necessarily need +to be a regular key, but can be any Key object, like the layer shift in the +example. Be aware however that key repeats are not suppressed. + ## Plugin compatibility If you're using AutoShift in a sketch that also includes the Qukeys and/or diff --git a/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.cpp b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.cpp index 2427828545..6a58a8ad9b 100644 --- a/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.cpp +++ b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.cpp @@ -23,6 +23,7 @@ #include "kaleidoscope/Runtime.h" // for Runtime, Runtime_ #include "kaleidoscope/key_defs.h" // for Key, Key_0, Key_1, Key_A, Key_F1, Key_F12, Key... #include "kaleidoscope/keyswitch_state.h" // for keyToggledOn, keyIsInjected +#include "kaleidoscope/progmem_helpers.h" // for cloneFromProgmem // IWYU pragma: no_include "HIDAliases.h" @@ -57,6 +58,11 @@ bool AutoShift::enabledForKey(Key key) { if (!key.isKeyboardKey()) return false; + // Check whether we have an explicit mapping for that key + if (isExplicitlyMapped(key)) { + return true; + } + // 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)`. @@ -93,6 +99,24 @@ bool AutoShift::enabledForKey(Key key) { return false; } +bool AutoShift::isExplicitlyMapped(Key key) { + // Check whether the given key has an explicit mapping to a different one + for (uint8_t i{0}; i < explicitmappings_count_; ++i) { + LongPress mappedKey = cloneFromProgmem(explicitmappings_[i]); + if (mappedKey.key == key) { + // cache the mapped key to not have to search it again + mapped_key_.key = mappedKey.key; + mapped_key_.alternate_key = mappedKey.alternate_key; + return true; + } + } + + // If no matches were found, clear mapped_key_ and return false + mapped_key_.key = Key_Transparent; + mapped_key_.alternate_key = Key_Transparent; + return false; +} + // ============================================================================= // Event handler hook functions @@ -187,10 +211,18 @@ void AutoShift::flushEvent(bool is_long_press) { 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); + event.key = Runtime.lookupKey(event.addr); + + // If we have an explicit mapping for that key, apply that. + if (mapped_key_.key != Key_Transparent) { + event.key = mapped_key_.alternate_key; + } else { + // If there was no explicit mapping, just add the shift modifier + // event.key = longpresses[event.key] + uint8_t flags = event.key.getFlags(); + flags ^= SHIFT_HELD; + event.key.setFlags(flags); + } } queue_.shift(); Runtime.handleKeyswitchEvent(event); diff --git a/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.h b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.h index 38d7c87911..a87d1ed027 100644 --- a/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.h +++ b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.h @@ -29,6 +29,22 @@ namespace kaleidoscope { namespace plugin { +struct LongPress { + // The key that should result in a different value on long press. + Key key; + // The alternate Key value that should be produced on long press. + Key alternate_key; + + // This is the constructor that should be used when creating a LongPress object in + // the PROGMEM array that will be used by explicit mappings (i.e. in the `AUTOSHIFT()` + // macro). + constexpr LongPress(Key key, Key alternate_key) + : key(key), alternate_key(alternate_key) {} + // This constructor is here so that we can create an empty LongPress object in RAM + // into which we can copy the values from a PROGMEM LongPress object. + LongPress() = default; +}; + // ============================================================================= /// Kaleidoscope plugin for long-press auto-shift keys /// @@ -232,6 +248,12 @@ class AutoShift : public Plugin { EventHandlerResult onKeyswitchEvent(KeyEvent &event); EventHandlerResult afterEachCycle(); + template + void configureLongPresses(LongPress const (&explicitmappings)[_explicitmappings_count]) { + explicitmappings_ = explicitmappings; + explicitmappings_count_ = _explicitmappings_count; + } + private: // --------------------------------------------------------------------------- /// A container for AutoShift configuration settings @@ -268,6 +290,16 @@ class AutoShift : public Plugin { /// The default function for `isAutoShiftable()` bool enabledForKey(Key key); + + bool isExplicitlyMapped(Key key); + + // An array of LongPress objects in PROGMEM. + LongPress const *explicitmappings_{nullptr}; + uint8_t explicitmappings_count_{0}; + + // A cache of the current explicit config key values, so we + // don't have to keep looking them up from PROGMEM. + LongPress mapped_key_ = {.key = Key_Transparent, .alternate_key = Key_Transparent}; }; // ============================================================================= @@ -288,3 +320,10 @@ class AutoShiftConfig : public Plugin { extern kaleidoscope::plugin::AutoShift AutoShift; extern kaleidoscope::plugin::AutoShiftConfig AutoShiftConfig; + +#define AUTOSHIFT(longpress_defs...) \ + { \ + static kaleidoscope::plugin::LongPress const qk_table[] PROGMEM = { \ + longpress_defs}; \ + AutoShift.configureLongPresses(qk_table); \ + } diff --git a/tests/plugins/AutoShift/explicit-config/explicit-config.ino b/tests/plugins/AutoShift/explicit-config/explicit-config.ino new file mode 100644 index 0000000000..9975a4aa59 --- /dev/null +++ b/tests/plugins/AutoShift/explicit-config/explicit-config.ino @@ -0,0 +1,54 @@ +/* -*- 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 . + */ + +#include +#include + +// *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); + + AUTOSHIFT( + kaleidoscope::plugin::LongPress(Key_A, Key_C), + ) +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/plugins/AutoShift/explicit-config/sketch.json b/tests/plugins/AutoShift/explicit-config/sketch.json new file mode 100644 index 0000000000..8cc869221a --- /dev/null +++ b/tests/plugins/AutoShift/explicit-config/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} diff --git a/tests/plugins/AutoShift/explicit-config/sketch.yaml b/tests/plugins/AutoShift/explicit-config/sketch.yaml new file mode 100644 index 0000000000..4d94810065 --- /dev/null +++ b/tests/plugins/AutoShift/explicit-config/sketch.yaml @@ -0,0 +1 @@ +default_fqbn: keyboardio:virtual:model01 diff --git a/tests/plugins/AutoShift/explicit-config/test.ktest b/tests/plugins/AutoShift/explicit-config/test.ktest new file mode 100644 index 0000000000..4a433e6250 --- /dev/null +++ b/tests/plugins/AutoShift/explicit-config/test.ktest @@ -0,0 +1,52 @@ +VERSION 1 + +KEYSWITCH LSHIFT 0 0 +KEYSWITCH RSHIFT 0 1 +KEYSWITCH A 1 0 +KEYSWITCH B 1 1 + +# ============================================================================== +NAME AutoShift explicit config + +RUN 4 ms +PRESS A +RUN 1 cycle + +# Timeout is 20ms +RUN 20 ms +EXPECT keyboard-report Key_C # report: { 6 } + +RUN 4 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 5 ms + +# ============================================================================== +NAME AutoShift explicit config shifted + +RUN 4 ms +PRESS LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } + +RUN 4 ms +PRESS A +RUN 1 cycle + +# Timeout is 20ms +RUN 20 ms +EXPECT keyboard-report Key_LeftShift Key_C # report: { 6 e1 } + +RUN 4 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } + +RUN 4 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 5 ms