diff --git a/docs/NEWS.md b/docs/NEWS.md index 56841b76..c28fefdd 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -215,6 +215,10 @@ To make it easier to port Kaleidoscope, we introduced the `ATMegaKeyboard` base ## New plugins +### AutoShift + +The [AutoShift](plugins/Kaleidoscope-AutoShift.md) plugin provides an alternative way to get shifted symbols, by long-pressing keys instead of using a separate `shift` key. + ### DynamicMacros The [DynamicMacros](plugins/Kaleidoscope-DynamicMacros.md) plugin provides a way to use and update macros via the Focus API, through Chrysalis. diff --git a/examples/Keystrokes/AutoShift/AutoShift.ino b/examples/Keystrokes/AutoShift/AutoShift.ino new file mode 100644 index 00000000..4cc2ce88 --- /dev/null +++ b/examples/Keystrokes/AutoShift/AutoShift.ino @@ -0,0 +1,75 @@ +// -*- mode: c++ -*- + +#include + +#include +#include +#include +#include +#include + +enum { + TOGGLE_AUTOSHIFT, +}; + +// *INDENT-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, + XXX, + + M(TOGGLE_AUTOSHIFT), 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, + XXX + ), +) +// *INDENT-ON* + +// Defining a macro (on the "any" key: see above) to turn AutoShift on and off +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + switch (macro_id) { + case TOGGLE_AUTOSHIFT: + if (keyToggledOn(event.state)) + AutoShift.toggle(); + break; + } + return MACRO_NONE; +} + +KALEIDOSCOPE_INIT_PLUGINS( + EEPROMSettings, // for AutoShiftConfig + EEPROMKeymap, // for AutoShiftConfig + Focus, // for AutoShiftConfig + FocusEEPROMCommand, // for AutoShiftConfig + FocusSettingsCommand, // for AutoShiftConfig + AutoShift, + AutoShiftConfig, // for AutoShiftConfig + Macros // for toggle AutoShift Macro +); + +void setup() { + // Enable AutoShift for letter keys and number keys only: + AutoShift.setEnabled(AutoShift.letterKeys() | AutoShift.numberKeys()); + // Add symbol keys to the enabled categories: + AutoShift.enable(AutoShift.symbolKeys()); + // Set the AutoShift long-press time to 150ms: + AutoShift.setTimeout(150); + // Start with AutoShift turned off: + AutoShift.disable(); + + Kaleidoscope.setup(); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/examples/Keystrokes/AutoShift/sketch.json b/examples/Keystrokes/AutoShift/sketch.json new file mode 100644 index 00000000..884ed009 --- /dev/null +++ b/examples/Keystrokes/AutoShift/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:avr:model01", + "port": "" + } +} diff --git a/plugins/Kaleidoscope-AutoShift/README.md b/plugins/Kaleidoscope-AutoShift/README.md new file mode 100644 index 00000000..650c23ce --- /dev/null +++ b/plugins/Kaleidoscope-AutoShift/README.md @@ -0,0 +1,86 @@ +# AutoShift + +AutoShift allows you to type shifted characters by long-pressing a key, rather +than chording it with a modifier key. + +## Using the plugin + +Using the plugin with its defaults is as simple as including the header, and +enabling the plugin: + +```c++ +#include +#include + +KALEIDOSCOPE_INIT_PLUGINS(AutoShift); +``` + +With AutoShift enabled, when you first press a key that AutoShift acts on, its +output will be delayed. If you hold the key down long enough, you will see the +shifted symbol appear in the output. If you release the key before the timeout, +the output will be unshifted. + +## Turning AutoShift on and off + +The `AutoShift` object provides three methods for turning itself on and off: + +- To turn the plugin off, call `AutoShift.enable()`. +- To turn the plugin on, call `AutoShift.disable()`. +- To toggle the plugin's state, call `AutoShift.toggle()`. + +Note: Disabling the AutoShift plugin does not affect which `Key` categories it +will affect when it is re-enabled. + +## Setting the AutoShift long-press delay + +To set the length of time AutoShift will wait before adding the `shift` modifier +to the key's output, use `AutoShift.setTimeout(t)`, where `t` is a number of +milliseconds. + +## Configuring which keys get auto-shifted + +AutoShift provides a set of key categories that can be independently added or +removed from the set of keys that will be auto-shifted when long-pressed: + +- `AutoShift.letterKeys()`: Letter keys +- `AutoShift.numberKeys()`: Number keys (number row, not numeric keypad) +- `AutoShift.symbolKeys()`: Other printable symbols +- `AutoShift.arrowKeys()`: Navigational arrow keys +- `AutoShift.functionKeys()`: All function keys (F1-F24) +- `AutoShift.printableKeys()`: Letters, numbers, and symbols +- `AutoShift.allKeys()`: All non-modifier USB Keyboard keys + +These categories are restricted to USB Keyboard-type keys, and any modifier +flags attached to the key is ignored when determining if it will be +auto-shifted. Any of the above expressions can be used as the `category` parameter in the functions described below. + +- To include a category in the set that will be auto-shifted, call `AutoShift.enable(category)` +- To remove a category from the set that will be auto-shifted, call `AutoShift.disable(category)` +- To set the full list of categories that will be auto-shifted, call `AutoShift.setEnabled(categories)`, where `categories` can be either a single category from the above list, or list of categories combined using the `|` (bitwise-or) operator (e.g. `AutoShift.setEnabled(AutoShift.letterKeys() | AutoShift.numberKeys())`). + +## Advanced customization of which keys get auto-shifted + +If the above categories are not sufficient for your auto-shifting needs, it is +possible to get even finer-grained control of which keys are affected by +AutoShift, by overriding the `isAutoShiftable()` method in your sketch. For +example, to make AutoShift only act on keys `A` and `Z`, include the following +code in your sketch: + +```c++ +bool AutoShift::isAutoShiftable(Key key) { + if (key == Key_A || key == key_Z) + return true; + return false; +} +``` + +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). + +## Further reading + +Starting from the [example][plugin:example] is the recommended way of getting +started with the plugin. + + [plugin:example]: /examples/Keystrokes/AutoShift/AutoShift.ino diff --git a/plugins/Kaleidoscope-AutoShift/library.properties b/plugins/Kaleidoscope-AutoShift/library.properties new file mode 100644 index 00000000..3fe6f349 --- /dev/null +++ b/plugins/Kaleidoscope-AutoShift/library.properties @@ -0,0 +1,7 @@ +name=Kaleidoscope-AutoShift +version=0.0.0 +sentence=Automatic shift on long press +maintainer=Kaleidoscope's Developers +url=https://github.com/keyboardio/Kaleidoscope +author=Michael Richters +paragraph= diff --git a/plugins/Kaleidoscope-AutoShift/src/Kaleidoscope-AutoShift.h b/plugins/Kaleidoscope-AutoShift/src/Kaleidoscope-AutoShift.h new file mode 100644 index 00000000..3a7adee3 --- /dev/null +++ b/plugins/Kaleidoscope-AutoShift/src/Kaleidoscope-AutoShift.h @@ -0,0 +1,20 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-AutoShift -- Automatic shift on long press + * 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 . + */ + +#pragma once + +#include diff --git a/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.cpp b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.cpp new file mode 100644 index 00000000..2c0c4df4 --- /dev/null +++ b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.cpp @@ -0,0 +1,212 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-AutoShift -- Automatic shift on long press + * 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 "kaleidoscope/plugin/AutoShift.h" + +#include "kaleidoscope/KeyAddr.h" +#include "kaleidoscope/key_defs.h" +#include "kaleidoscope/KeyEvent.h" +#include "kaleidoscope/keyswitch_state.h" +#include "kaleidoscope/Runtime.h" + +namespace kaleidoscope { +namespace plugin { + +// ============================================================================= +// AutoShift static class variables + +// Configuration settings that can be saved to persistent storage. +AutoShift::Settings AutoShift::settings_ = { + .enabled = true, + .timeout = 175, + .enabled_categories = AutoShift::Categories::printableKeys(), +}; + +// The event tracker ensures that the `onKeyswitchEvent()` handler will follow +// the rules in order to avoid interference with other plugins and prevent +// processing the same event more than once. +KeyEventTracker AutoShift::event_tracker_; + +// If there's a delayed keypress from AutoShift, this stored event will contain +// a valid `KeyAddr`. The default constructor produces an event addr of +// `KeyAddr::none()`, so the plugin will start in an inactive state. +KeyEvent pending_event_; + +// ============================================================================= +// AutoShift functions + +void AutoShift::disable() { + settings_.enabled = false; + if (pending_event_.addr.isValid()) { + Runtime.handleKeyswitchEvent(pending_event_); + } +} + +// ----------------------------------------------------------------------------- +// Test for whether or not to apply AutoShift to a given `Key`. This function +// can be overridden from the user sketch. +__attribute__((weak)) +bool AutoShift::isAutoShiftable(Key key) { + return enabledForKey(key); +} + +// The default method that determines whether a particular key is an auto-shift +// candidate. Used by `isAutoShiftable()`, separate to allow re-use when the +// caller is overridden. +bool AutoShift::enabledForKey(Key key) { + // We only support auto-shifting keyboard keys. We could also explicitly + // ignore modifier keys, but there's no need to do so, as none of the code + // below matches modifiers. + if (!key.isKeyboardKey()) + return false; + + // 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)`. + uint8_t keycode = key.getKeyCode(); + + if (isEnabled(Categories::allKeys())) { + if (keycode < HID_KEYBOARD_FIRST_MODIFIER) + return true; + } + if (isEnabled(Categories::letterKeys())) { + if (keycode >= Key_A.getKeyCode() && keycode <= Key_Z.getKeyCode()) + return true; + } + if (isEnabled(Categories::numberKeys())) { + if (keycode >= Key_1.getKeyCode() && keycode <= Key_0.getKeyCode()) + return true; + } + if (isEnabled(Categories::symbolKeys())) { + if ((keycode >= Key_Minus.getKeyCode() && keycode <= Key_Slash.getKeyCode()) || + (keycode == Key_NonUsBackslashAndPipe.getKeyCode())) + return true; + } + if (isEnabled(Categories::arrowKeys())) { + if (keycode >= Key_RightArrow.getKeyCode() && + keycode <= Key_LeftArrow.getKeyCode()) + return true; + } + if (isEnabled(Categories::functionKeys())) { + if ((keycode >= Key_F1.getKeyCode() && keycode <= Key_F12.getKeyCode()) || + (keycode >= Key_F13.getKeyCode() && keycode <= Key_F24.getKeyCode())) + return true; + } + + return false; +} + +// ============================================================================= +// Event handler hook functions + +// ----------------------------------------------------------------------------- +EventHandlerResult AutoShift::onKeyswitchEvent(KeyEvent &event) { + // If AutoShift has already processed and released this event, ignore it. + // There's no need to update the event tracker in this one case. + if (event_tracker_.shouldIgnore(event)) { + // We should never get an event that's in our queue here, but just in case + // some other plugin sends one, abort. + if (queue_.shouldAbort(event)) + return EventHandlerResult::ABORT; + return EventHandlerResult::OK; + } + + // If event.addr is not a physical key, ignore it; some other plugin injected + // it. This check should be unnecessary. + if (!event.addr.isValid() || (event.state & INJECTED) != 0) { + return EventHandlerResult::OK; + } + + // Do nothing if disabled. + if (!settings_.enabled) + return EventHandlerResult::OK; + + if (!queue_.isEmpty()) { + // There's an unresolved AutoShift key press. + if (queue_.isFull()) { + flushQueue(); + } else if (event.addr == queue_.addr(0)) { + flushEvent(false); + flushQueue(); + } + if (queue_.isEmpty()) + return EventHandlerResult::OK; + queue_.append(event); + return EventHandlerResult::ABORT; + } + + if (keyToggledOn(event.state) && isAutoShiftable(event.key)) { + queue_.append(event); + return EventHandlerResult::ABORT; + } + + return EventHandlerResult::OK; +} + +// ----------------------------------------------------------------------------- +EventHandlerResult AutoShift::afterEachCycle() { + // If there's a pending AutoShift event, and it has timed out, we need to + // release the event with the `shift` flag applied. + if (!queue_.isEmpty() && + Runtime.hasTimeExpired(queue_.timestamp(0), settings_.timeout)) { + // Toggle the state of the `SHIFT_HELD` bit in the modifier flags for the + // key for the pending event. + flushEvent(true); + flushQueue(); + } + return EventHandlerResult::OK; +} + +void AutoShift::flushQueue() { + while (!queue_.isEmpty()) { + if (queue_.isRelease(0) || checkForRelease()) { + flushEvent(false); + } else { + return; + } + } +} + +bool AutoShift::checkForRelease() const { + KeyAddr queue_head_addr = queue_.addr(0); + for (uint8_t i = 1; i < queue_.length(); ++i) { + if (queue_.addr(i) == queue_head_addr) { + // This key's release is also in the queue + return true; + } + } + return false; +} + +void AutoShift::flushEvent(bool is_long_press) { + if (queue_.isEmpty()) + 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); + } + queue_.shift(); + Runtime.handleKeyswitchEvent(event); +} + +} // namespace plugin +} // namespace kaleidoscope + +kaleidoscope::plugin::AutoShift AutoShift; diff --git a/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.h b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.h new file mode 100644 index 00000000..98749997 --- /dev/null +++ b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.h @@ -0,0 +1,278 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-AutoShift -- Automatic shift on long press + * 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 . + */ + +#pragma once + +#include "kaleidoscope/Runtime.h" +#include "kaleidoscope/KeyEventTracker.h" +#include "kaleidoscope/KeyAddrEventQueue.h" + +namespace kaleidoscope { +namespace plugin { + +// ============================================================================= +/// Kaleidoscope plugin for long-press auto-shift keys +/// +/// This plugin allows the user to "long-press" keys to automatically apply the +/// `shift` modifier to the keypress. By enabling AutoShift, it's possible to +/// produce capital letters (for example) without holding a separate modifier +/// key first. +class AutoShift : public Plugin { + + public: + // --------------------------------------------------------------------------- + // Inner class for `Key` categories that can be configured to be auto-shifted + // by long-pressing. Most of this class is purely internal, but user code + // that enables or disables these auto-shift categories might use the + // following as constants: + // + // - `AutoShift::Categories::letterKeys()` + // - `AutoShift::Categories::numberKeys()` + // - `AutoShift::Categories::symbolKeys()` + // - `AutoShift::Categories::arrowKeys()` + // - `AutoShift::Categories::functionKeys()` + // - `AutoShift::Categories::printableKeys()` + // - `AutoShift::Categories::allKeys()` + // + // The first two ("letter keys" and "number keys") are self-explanatory. The + // third ("symbol keys") includes keys that produce symbols other than letters + // and numbers, but not whitespace characters, modifiers, et cetera. We could + // perhaps add another category for function keys. + class Categories { + private: + // raw bitfield data + uint8_t raw_bits_{0}; + + // constants for bits in the `raw_bits_` bitfield + static constexpr uint8_t LETTERS = 1 << 0; + static constexpr uint8_t NUMBERS = 1 << 1; + static constexpr uint8_t SYMBOLS = 1 << 2; + static constexpr uint8_t ARROWS = 1 << 3; + static constexpr uint8_t FUNCTIONS = 1 << 4; + static constexpr uint8_t ALL = 1 << 7; + + public: + // Basic un-checked constructor + constexpr Categories(uint8_t raw_bits) : raw_bits_(raw_bits) {} + + static constexpr Categories letterKeys() { + return Categories(LETTERS); + } + static constexpr Categories numberKeys() { + return Categories(NUMBERS); + } + static constexpr Categories symbolKeys() { + return Categories(SYMBOLS); + } + static constexpr Categories arrowKeys() { + return Categories(ARROWS); + } + static constexpr Categories functionKeys() { + return Categories(FUNCTIONS); + } + static constexpr Categories printableKeys() { + return Categories(LETTERS | NUMBERS | SYMBOLS); + } + static constexpr Categories allKeys() { + return Categories(ALL); + } + + constexpr void set(uint8_t raw_bits) { + raw_bits_ = raw_bits; + } + constexpr void add(Categories categories) { + this->raw_bits_ |= categories.raw_bits_; + } + constexpr void remove(Categories categories) { + this->raw_bits_ &= ~(categories.raw_bits_); + } + constexpr bool contains(Categories categories) const { + return (this->raw_bits_ & categories.raw_bits_) != 0; + // More correct test: + // return (~(this->raw_bits_) & categories.raw_bits_) == 0; + } + + // Shorthand for combining categories: + // e.g. `Categories::letterKeys() | Categories::numberKeys()` + constexpr Categories operator|(Categories other) const { + return Categories(this->raw_bits_ | other.raw_bits_); + } + + // A conversion to integer is provided for the sake of interactions with the + // Focus plugin. + explicit constexpr operator uint8_t() { + return raw_bits_; + } + }; + + // --------------------------------------------------------------------------- + // This lets the AutoShiftConfig plugin access the internal config variables + // directly. Mainly useful for calls to `Runtime.storage.get()`. + friend class AutoShiftConfig; + + // --------------------------------------------------------------------------- + // Configuration functions + + /// Returns `true` if AutoShift is active, `false` otherwise + static bool enabled() { + return settings_.enabled; + } + /// Activates the AutoShift plugin (held keys will trigger auto-shift) + static void enable() { + settings_.enabled = true; + } + /// Deactivates the AutoShift plugin (held keys will not trigger auto-shift) + static void disable(); + /// Turns AutoShift on if it's off, and vice versa + static void toggle() { + if (settings_.enabled) { + disable(); + } else { + enable(); + } + } + + /// Returns the hold time required to trigger auto-shift (in ms) + static uint16_t timeout() { + return settings_.timeout; + } + /// Sets the hold time required to trigger auto-shift (in ms) + static void setTimeout(uint16_t new_timeout) { + settings_.timeout = new_timeout; + } + + /// Returns the set of categories currently eligible for auto-shift + static Categories enabledCategories() { + return settings_.enabled_categories; + } + /// Adds `category` to the set eligible for auto-shift + /// + /// Possible values for `category` are: + /// - `AutoShift::Categories::letterKeys()` + /// - `AutoShift::Categories::numberKeys()` + /// - `AutoShift::Categories::symbolKeys()` + /// - `AutoShift::Categories::arrowKeys()` + /// - `AutoShift::Categories::functionKeys()` + /// - `AutoShift::Categories::printableKeys()` + /// - `AutoShift::Categories::allKeys()` + static void enable(Categories category) { + settings_.enabled_categories.add(category); + } + /// Removes a `Key` category from the set eligible for auto-shift + static void disable(Categories category) { + settings_.enabled_categories.remove(category); + } + /// Replaces the list of `Key` categories eligible for auto-shift + static void setEnabled(Categories categories) { + settings_.enabled_categories = categories; + } + /// Returns `true` if the given category is eligible for auto-shift + static bool isEnabled(Categories category) { + return settings_.enabled_categories.contains(category); + } + + /// The category representing letter keys + static constexpr Categories letterKeys() { + return Categories::letterKeys(); + } + /// The category representing number keys (on the number row) + static constexpr Categories numberKeys() { + return Categories::numberKeys(); + } + /// The category representing other printable symbol keys + static constexpr Categories symbolKeys() { + return Categories::symbolKeys(); + } + /// The category representing arrow keys + static constexpr Categories arrowKeys() { + return Categories::arrowKeys(); + } + /// The category representing function keys + static constexpr Categories functionKeys() { + return Categories::functionKeys(); + } + /// Letters, numbers, and other symbols + static constexpr Categories printableKeys() { + return Categories::printableKeys(); + } + /// All non-modifier keyboard keys + static constexpr Categories allKeys() { + return Categories::allKeys(); + } + + // --------------------------------------------------------------------------- + /// Determines which keys will trigger auto-shift if held long enough + /// + /// This function can be overridden by the user sketch to configure which keys + /// can trigger auto-shift. + static bool isAutoShiftable(Key key); + + // --------------------------------------------------------------------------- + // Event handlers + EventHandlerResult onKeyswitchEvent(KeyEvent &event); + EventHandlerResult afterEachCycle(); + + private: + // --------------------------------------------------------------------------- + /// A container for AutoShift configuration settings + struct Settings { + /// The overall state of the plugin (on/off) + bool enabled; + /// The length of time (ms) a key must be held to trigger auto-shift + uint16_t timeout; + /// The set of `Key` categories eligible to be auto-shifted + Categories enabled_categories; + }; + static Settings settings_; + + // --------------------------------------------------------------------------- + // Key event queue state variables + + // A device for processing only new events + static KeyEventTracker event_tracker_; + + // The maximum number of events in the queue at a time. + static constexpr uint8_t queue_capacity_{4}; + + // The event queue stores a series of press and release events. + KeyAddrEventQueue queue_; + + void flushQueue(); + void flushEvent(bool is_long_press = false); + bool checkForRelease() const; + + /// The default function for `isAutoShiftable()` + static bool enabledForKey(Key key); +}; + +// ============================================================================= +/// Configuration plugin for persistent storage of settings +class AutoShiftConfig : public Plugin { + public: + EventHandlerResult onSetup(); + EventHandlerResult onFocusEvent(const char *command); + + private: + // The base address in persistent storage for configuration data + static uint16_t settings_base_; +}; + +} // namespace plugin +} // namespace kaleidoscope + +extern kaleidoscope::plugin::AutoShift AutoShift; +extern kaleidoscope::plugin::AutoShiftConfig AutoShiftConfig; diff --git a/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShiftConfig.cpp b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShiftConfig.cpp new file mode 100644 index 00000000..90f57dcc --- /dev/null +++ b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShiftConfig.cpp @@ -0,0 +1,121 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-AutoShift -- Automatic shift on long press + * 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 "kaleidoscope/plugin/AutoShift.h" + +#include +#include + +#include "kaleidoscope/Runtime.h" + +namespace kaleidoscope { +namespace plugin { + +// ============================================================================= +// AutoShift configurator + +uint16_t AutoShiftConfig::settings_base_; + +EventHandlerResult AutoShiftConfig::onSetup() { + settings_base_ = ::EEPROMSettings.requestSlice(sizeof(AutoShift::settings_)); + uint32_t checker; + + Runtime.storage().get(settings_base_, checker); + + // Check if we have an empty eeprom... + if (checker == 0xffffffff) { + // ...if the eeprom was empty, store the default settings. + Runtime.storage().put(settings_base_, AutoShift::settings_); + Runtime.storage().commit(); + } + + Runtime.storage().get(settings_base_, AutoShift::settings_); + return EventHandlerResult::OK; +} + +EventHandlerResult AutoShiftConfig::onFocusEvent(const char *command) { + enum { + ENABLED, + TIMEOUT, + CATEGORIES, + } subCommand; + + if (::Focus.handleHelp(command, PSTR("autoshift.enabled\n" + "autoshift.timeout\n" + "autoshift.categories"))) + return EventHandlerResult::OK; + + if (strncmp_P(command, PSTR("autoshift."), 10) != 0) + return EventHandlerResult::OK; + if (strcmp_P(command + 10, PSTR("enabled")) == 0) + subCommand = ENABLED; + else if (strcmp_P(command + 10, PSTR("timeout")) == 0) + subCommand = TIMEOUT; + else if (strcmp_P(command + 10, PSTR("categories")) == 0) + subCommand = CATEGORIES; + else + return EventHandlerResult::OK; + + switch (subCommand) { + case ENABLED: + if (::Focus.isEOL()) { + ::Focus.send(::AutoShift.enabled()); + } else { + uint8_t v; + ::Focus.read(v); + // if (::Focus.readUint8() != 0) { + if (v != 0) { + ::AutoShift.enable(); + } else { + ::AutoShift.disable(); + } + } + break; + + case TIMEOUT: + if (::Focus.isEOL()) { + ::Focus.send(::AutoShift.timeout()); + } else { + uint16_t t; + ::Focus.read(t); + // auto t = ::Focus.readUint16(); + ::AutoShift.setTimeout(t); + } + break; + + case CATEGORIES: + if (::Focus.isEOL()) { + ::Focus.send(uint8_t(::AutoShift.enabledCategories())); + } else { + uint8_t v; + ::Focus.read(v); + auto categories = AutoShift::Categories(v); + // auto categories = AutoShift::Categories(::Focus.readUint8()); + ::AutoShift.setEnabled(categories); + } + break; + } + + Runtime.storage().put(settings_base_, AutoShift::settings_); + Runtime.storage().commit(); + return EventHandlerResult::EVENT_CONSUMED; +} + +} // namespace plugin +} // namespace kaleidoscope + +kaleidoscope::plugin::AutoShiftConfig AutoShiftConfig; diff --git a/tests/plugins/AutoShift/basic/basic.ino b/tests/plugins/AutoShift/basic/basic.ino new file mode 100644 index 00000000..67118a12 --- /dev/null +++ b/tests/plugins/AutoShift/basic/basic.ino @@ -0,0 +1,50 @@ +/* -*- 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); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/plugins/AutoShift/basic/sketch.json b/tests/plugins/AutoShift/basic/sketch.json new file mode 100644 index 00000000..8cc86922 --- /dev/null +++ b/tests/plugins/AutoShift/basic/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} diff --git a/tests/plugins/AutoShift/basic/test.ktest b/tests/plugins/AutoShift/basic/test.ktest new file mode 100644 index 00000000..1a336e26 --- /dev/null +++ b/tests/plugins/AutoShift/basic/test.ktest @@ -0,0 +1,41 @@ +VERSION 1 + +KEYSWITCH LSHIFT 0 0 +KEYSWITCH RSHIFT 0 1 +KEYSWITCH A 1 0 +KEYSWITCH B 1 1 + +# ============================================================================== +NAME AutoShift tap + +RUN 4 ms +PRESS A +RUN 1 cycle + +RUN 4 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report Key_A # report: { 4 } +EXPECT keyboard-report empty + +RUN 5 ms + +# ============================================================================== +NAME AutoShift long press + +RUN 4 ms +PRESS A +RUN 1 cycle + +# Timeout is 20ms +RUN 20 ms +EXPECT keyboard-report Key_LeftShift # report: { e1 } +EXPECT keyboard-report Key_LeftShift Key_A # report: { 4 e1 } + +RUN 4 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } +EXPECT keyboard-report empty + +RUN 5 ms