/* -*- mode: c++ -*-
* Kaleidoscope-Qukeys -- Assign two keycodes to a single key
* Copyright (C) 2017-2020 Michael Richters
*
* 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 .
*/
#pragma once
#include // for PROGMEM
#include // for DUM_FIRST
#include // for uint8_t, uint16_t
#include "kaleidoscope/KeyAddr.h" // for KeyAddr
#include "kaleidoscope/KeyAddrEventQueue.h" // for KeyAddrEventQueue
#include "kaleidoscope/KeyEvent.h" // for KeyEvent
#include "kaleidoscope/KeyEventTracker.h" // for KeyEventTracker
#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult
#include "kaleidoscope/key_defs.h" // for Key, Key_Transparent
#include "kaleidoscope/plugin.h" // for Plugin
// DualUse Key definitions for Qukeys in the keymap
#define MT(mod, key) kaleidoscope::plugin::ModTapKey(Key_##mod, Key_##key)
#define SFT_T(key) MT(LeftShift, key)
#define CTL_T(key) MT(LeftControl, key)
#define ALT_T(key) MT(LeftAlt, key)
#define GUI_T(key) MT(LeftGui, key)
#define LT(layer, key) kaleidoscope::plugin::LayerTapKey(layer, Key_##key)
namespace kaleidoscope {
namespace plugin {
constexpr Key ModTapKey(Key mod_key, Key tap_key) {
uint8_t mod = mod_key.getKeyCode() - HID_KEYBOARD_FIRST_MODIFIER;
return Key(kaleidoscope::ranges::DUM_FIRST +
(mod << 8) + tap_key.getKeyCode());
}
constexpr Key LayerTapKey(uint8_t layer, Key tap_key) {
return Key(kaleidoscope::ranges::DUL_FIRST +
(layer << 8) + tap_key.getKeyCode());
}
// Data structure for an individual qukey
struct Qukey {
// The layer this qukey is mapped on.
int8_t layer;
// The keyswitch address of the qukey.
KeyAddr addr;
// The alternake Key value this qukey should use (when held).
Key alternate_key;
// This is the constructor that should be used when creating a Qukey object in
// the PROGMEM array that will be used by Qukeys (i.e. in the `QUKEYS()`
// macro).
constexpr Qukey(int8_t layer, KeyAddr k, Key alternate_key)
: layer(layer), addr(k), alternate_key(alternate_key) {}
// This constructor is here so that we can create an empty Qukey object in RAM
// into which we can copy the values from a PROGMEM Qukey object.
Qukey() = default;
};
class Qukeys : public kaleidoscope::Plugin {
public:
// Methods for turning the plugin on and off.
void activate() {
active_ = true;
}
void deactivate() {
active_ = false;
}
void toggle() {
active_ = !active_;
}
// Set the timeout (in milliseconds) for a held qukey. If a qukey is held at
// least this long, it will take on its alternate value (unless its primary
// value is a modifier -- i.e. a SpaceCadet type key).
void setHoldTimeout(uint16_t hold_timeout) {
hold_timeout_ = hold_timeout;
}
// Set the timeout (in milliseconds) for the tap-repeat feature. If a qukey is
// tapped twice in a row in less time than this amount, it will allow the user
// to hold the key with its primary value (unless it's a SpaceCadet type key,
// in which case it will repeat the alternate value instead). This requires a
// quick tap immediately followed by a press and hold, and will result in a
// single press-and-hold on the host system. If a double tap is done instead,
// it will result in two independent taps.
void setMaxIntervalForTapRepeat(uint8_t ttl) {
tap_repeat_.timeout = ttl;
}
// Set the percentage of the duration of a subsequent key's press that must
// overlap with the qukey preceding it above which the qukey will take on its
// alternate key value. In other words, if the user presses qukey `Q`, then
// key `K`, and the overlap of the two keys (`O`) is less than this
// percentage, the qukey will take on its primary value, but if it's greater
// (i.e. `K` spends a very small fraction of its time pressed after the `Q` is
// released), the qukey will take on its alternate value.
void setOverlapThreshold(uint8_t percentage) {
// Only percentages less than 100% are meaningful. 0% means to turn off the
// rollover grace period and rely solely on release order to determine the
// qukey's state.
if (percentage < 100) {
overlap_threshold_ = percentage;
} else {
overlap_threshold_ = 0;
}
}
// Set the minimum length of time a qukey must be held before it can resolve
// to its alternate key value. If a qukey is pressed and released in less than
// this number of milliseconds, it will always produce its primary key value.
void setMinimumHoldTime(uint8_t min_hold_time) {
minimum_hold_time_ = min_hold_time;
}
// Set the minimum interval between the previous keypress and the qukey press
// to make the qukey eligible to become its alternate keycode.
void setMinimumPriorInterval(uint8_t min_interval) {
minimum_prior_interval_ = min_interval;
}
// Function for defining the array of qukeys data (in PROGMEM). It's a
// template function that takes as its sole argument an array reference of
// size `_qukeys_count`, so there's no need to use `sizeof` to calculate the
// correct size, and pass it as a separate parameter.
template
void configureQukeys(Qukey const (&qukeys)[_qukeys_count]) {
qukeys_ = qukeys;
qukeys_count_ = _qukeys_count;
}
// A wildcard value for a qukey that exists on every layer.
static constexpr int8_t layer_wildcard{-1};
// Kaleidoscope hook functions.
EventHandlerResult onNameQuery();
EventHandlerResult onKeyswitchEvent(KeyEvent &event);
EventHandlerResult afterEachCycle();
private:
// An array of Qukey objects in PROGMEM.
Qukey const *qukeys_{nullptr};
uint8_t qukeys_count_{0};
// The maximum number of events in the queue at a time.
static constexpr uint8_t queue_capacity_{8};
// The event queue stores a series of press and release events.
KeyAddrEventQueue event_queue_;
// This determines whether the plugin is on or off.
bool active_{true};
// This variable stores the percentage number between 0 and 99 that determines
// how forgiving the plugin is of rollover from a qukey to a modified key.
uint8_t overlap_threshold_{80};
// The number of milliseconds until a qukey held on its own will take on its
// alternate state (or primary state, in the case of a SpaceCadet-type qukey).
uint16_t hold_timeout_{250};
// The minimum number of milliseconds a qukey must be held before it is
// allowed to take on its alternate key value (to limit unintended modifiers
// for very fast typists).
uint8_t minimum_hold_time_{50};
// The minimum interval in milliseconds between the previous keypress and the
// press of a qukey required to make the qukey eligible to take on its
// alternate value.
uint8_t minimum_prior_interval_{75};
// Timestamp of the keypress event immediately prior to the queue head event.
// The initial value is 256 to ensure that it won't trigger an error if a
// qukey is pressed before `minimum_prior_interval_` milliseconds after the
// keyboard powers on, and that value can only be as high as 255.
uint16_t prior_keypress_timestamp_{256};
// This is a guard against re-processing events when qukeys flushes them from
// its event queue. We can't just use an "injected" key state flag, because
// that would cause other plugins to also ignore the event.
KeyEventTracker event_tracker_;
// A cache of the current qukey's primary and alternate key values, so we
// don't have to keep looking them up from PROGMEM.
struct {
Key primary_key{Key_Transparent};
Key alternate_key{Key_Transparent};
} queue_head_;
// Internal helper methods.
bool processQueue();
void flushEvent(Key event_key);
bool isQukey(KeyAddr k);
bool isDualUseKey(Key key);
bool releaseDelayed(uint16_t overlap_start, uint16_t overlap_end) const;
bool isKeyAddrInQueueBeforeIndex(KeyAddr k, uint8_t index) const;
// Tap-repeat feature support.
struct {
KeyAddr addr{KeyAddr::invalid_state};
uint16_t start_time;
uint8_t timeout{200};
} tap_repeat_;
bool shouldWaitForTapRepeat();
};
// This function returns true for any key that we expect to be used chorded with
// a "modified" key. This includes actual keyboard modifiers, but also layer
// shift keys. Used for determining if a qukey is a SpaceCadet-type key.
bool isModifierKey(Key key);
} // namespace plugin
} // namespace kaleidoscope
extern kaleidoscope::plugin::Qukeys Qukeys;
// Macro for use in sketch file to simplify definition of the qukeys array and
// guarantee that the count is set correctly. This is considerably less
// important than it used to be, with the `configureQukeys()` function taking
// care of guaranteeing the correct count setting.
#define QUKEYS(qukey_defs...) \
{ \
static kaleidoscope::plugin::Qukey const qk_table[] PROGMEM = { \
qukey_defs}; \
Qukeys.configureQukeys(qk_table); \
}