Merge remote-tracking branch 'plugin/Qukeys/f/monorepo' into f/monorepo-stage2

pull/389/head
Gergely Nagy 6 years ago
commit b0e3db9328
No known key found for this signature in database
GPG Key ID: AC1E90BAC433F68F

@ -0,0 +1,158 @@
# Kaleidoscope-Qukeys
## Concept
This Kaleidoscope plugin allows you to overload keys on your keyboard so that they produce
one keycode (i.e. symbol) when tapped, and a different keycode -- most likely a modifier
(e.g. `shift` or `alt`) -- when held.
## Setup
- Include the header file:
```
#include <Kaleidoscope-Qukeys.h>
```
- Use the plugin in the `KALEIDOSCOPE_INIT_PLUGINS` macro:
```
KALEIDOSCOPE_INIT_PLUGINS(Qukeys);
```
- Define some `Qukeys` of the format `Qukey(layer, row, col, alt_keycode)`
(layers, rows and columns are all zero-indexed, rows are top to bottom and
columns are left to right):
- For the Keyboardio Model 01, key coordinates refer to [this header
file](Hardware-Model01/blob/f469015346535cb864a340bf8eb317d268943248/src/Kaleidoscope-Hardware-Model01.h#L267-L279)..md
```
QUKEYS(
// l, r, c, alt_keycode
kaleidoscope::plugin::Qukey(0, 2, 1, Key_LeftGui), // A/cmd
kaleidoscope::plugin::Qukey(0, 2, 2, Key_LeftAlt), // S/alt
kaleidoscope::plugin::Qukey(0, 2, 3, Key_LeftControl), // D/ctrl
kaleidoscope::plugin::Qukey(0, 2, 4, Key_LeftShift), // F/shift
kaleidoscope::plugin::Qukey(0, 1, 14, Key_LeftShift), // P/shift
kaleidoscope::plugin::Qukey(0, 3, 15, Key_LeftShift) // Minus/shift
)
```
`Qukeys` will work best if it's the first plugin in the `INIT()` list, because when typing
overlap occurs, it will (temporarily) mask keys and block them from being processed by
other plugins. If those other plugins handle the keypress events first, it may not work as
expected. It doesn't _need_ to be first, but if it's `INIT()`'d after another plugin that
handles typing events, especially one that sends extra keyboard HID reports, it is more
likely to generate errors and out-of-order events.
## Configuration
### `.setTimeout(time_limit)`
> Sets the time length in milliseconds which determines if a key has been tapped or held.
>
> Defaults to 250.
### `.setReleaseDelay(release_delay)`
> Sets the time length in milliseconds to artificially delay the release of the Qukey.
>
> This is to accommodate users who are in the habit of releasing modifiers and the keys
> they modify (almost) simultaneously, since the Qukey may be detected as released
> *slightly* before the other key, which would not trigger the desired alternate keycode.
>
> It is best to keep this a very small value such as 20 to avoid over-extending the
> modifier to further keystrokes.
>
> Defaults to 0.
### `.activate()`
### `.deactivate()`
### `.toggle()`
> activate/deactivate `Qukeys`
### DualUse key definitions
In addition to normal `Qukeys` described above, Kaleidoscope-Qukeys also treats
DualUse keys in the keymap as `Qukeys`. This makes `Qukeys` a drop-in replacement
for the `DualUse` plugin, without the need to edit the keymap.
The plugin provides a number of macros one can use in keymap definitions:
#### `CTL_T(key)`
> A key that acts as the *left* `Control` when held, or used in conjunction with
> other keys, but as `key` when tapped in isolation. The `key` argument must be
> a plain old key, and can't have any modifiers or anything else applied.
#### `ALT_T(key)`
> A key that acts as the *left* `Alt` when held, or used in conjunction with
> other keys, but as `key` when tapped in isolation. The `key` argument must be
> a plain old key, and can't have any modifiers or anything else applied.
#### `SFT_T(key)`
> A key that acts as the *left* `Shift` when held, or used in conjunction with
> other keys, but as `key` when tapped in isolation. The `key` argument must be
> a plain old key, and can't have any modifiers or anything else applied.
#### `GUI_T(key)`
> A key that acts as the *left* `GUI` when held, or used in conjunction with
> other keys, but as `key` when tapped in isolation. The `key` argument must be
> a plain old key, and can't have any modifiers or anything else applied.
#### `MT(mod, key)`
> A key that acts as `mod` when held, or used in conjunction with other keys,
> but as `key` when tapped in isolation. The `key` argument must be a plain old
> key, and can't have any modifiers or anything else applied. The `mod` argument
> can be any of the modifiers, *left* or *right* alike.
#### `LT(layer, key)`
> A key that momentarily switches to `layer` when held, or used in conjunction
> with other keys, but as `key` when tapped in isolation. The `key` argument
> must be a plain old key, and can't have any modifiers or anything else
> applied.
## Design & Implementation
When a `Qukey` is pressed, it doesn't immediately add a corresponding keycode to the HID
report; it adds that key to a queue, and waits until one of three things happens:
1. a time limit is reached
2. the `Qukey` is released
3. a subsequently-pressed key is released
Until one of those conditions is met, all subsequent keypresses are simply added to the
queue, and no new reports are sent to the host. Once a condition is met, the `Qukey` is
flushed from the queue, and so are any subsequent keypresses (up to, but not including,
the next `Qukey` that is still pressed).
Basically, if you hold the `Qukey`, then press and release some other key, you'll get the
alternate keycode (probably a modifier) for the `Qukey`, even if you don't wait for a
timeout. If you're typing quickly, and there's some overlap between two keypresses, you
won't get the alternate keycode, and the keys will be reported in the order that they were
pressed -- as long as the keys are released in the same order they were pressed.
The time limit is mainly there so that a `Qukey` can be used as a modifier (in its
alternate state) with a second input device (e.g. a mouse). It can be quite short (200ms
is probably short enough) -- as long as your "taps" while typing are shorter than the time
limit, you won't get any unintended alternate keycodes.
## Further reading
The [example][plugin:example] can help to learn how to use this plugin.
[plugin:example]: ../../examples/Qukeys/Qukeys.ino
## Upgrading
Previous versions of `Qukeys` used `kaleidoscope::Qukey` objects within the
`QUKEYS` macro. In newer versions, this is `kaleidoscope::plugin::Qukey`
instead. The old name still works, but will be removed by 2019-01-14.

@ -0,0 +1,81 @@
// -*- mode: c++ -*-
#include <Kaleidoscope.h>
#include <Kaleidoscope-Qukeys.h>
#include <Kaleidoscope-Macros.h>
enum { MACRO_TOGGLE_QUKEYS };
// *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,
Key_Q,
M(MACRO_TOGGLE_QUKEYS), 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, SFT_T(J), CTL_T(K), ALT_T(L), GUI_T(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,
LT(1,E)
),
[1] = KEYMAP_STACKED
(
___, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F,
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
Key_1, Key_2, Key_3, Key_4,
___,
___, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F,
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
Key_1, Key_2, Key_3, Key_4,
___
),
)
// *INDENT-ON*
// Defining a macro (on the "any" key: see above) to toggle Qukeys on and off
const macro_t *macroAction(uint8_t macro_index, uint8_t key_state) {
switch (macro_index) {
case MACRO_TOGGLE_QUKEYS:
if (keyToggledOn(key_state))
Qukeys.toggle();
break;
}
return MACRO_NONE;
}
// Use Qukeys
KALEIDOSCOPE_INIT_PLUGINS(Qukeys, Macros);
void setup() {
QUKEYS(
kaleidoscope::plugin::Qukey(0, 2, 1, Key_LeftGui), // A/cmd
kaleidoscope::plugin::Qukey(0, 2, 2, Key_LeftAlt), // S/alt
kaleidoscope::plugin::Qukey(0, 2, 3, Key_LeftControl), // D/ctrl
kaleidoscope::plugin::Qukey(0, 2, 4, Key_LeftShift), // F/shift
kaleidoscope::plugin::Qukey(0, 3, 6, ShiftToLayer(1)) // Q/layer-shift (on `fn`)
)
Qukeys.setTimeout(200);
Qukeys.setReleaseDelay(20);
Kaleidoscope.setup();
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,21 @@
/* -*- mode: c++ -*-
* Kaleidoscope-Qukeys -- Assign two keycodes to a single key
* Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <kaleidoscope/plugin/Qukeys.h>

@ -0,0 +1,44 @@
/* -*- mode: c++ -*-
* Kaleidoscope-Qukeys -- Assign two keycodes to a single key
* Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Kaleidoscope.h>
// Helper functions for converting between separate (row,col)
// coordinates and a single-byte key number (addr). This works as long
// as the keyboard has fewer than 256 keys.
namespace kaleidoscope {
namespace addr {
inline uint8_t row(uint8_t key_addr) {
return (key_addr / COLS);
}
inline uint8_t col(uint8_t key_addr) {
return (key_addr % COLS);
}
inline uint8_t addr(uint8_t row, uint8_t col) {
return ((row * COLS) + col);
}
inline void mask(uint8_t key_addr) {
KeyboardHardware.maskKey(row(key_addr), col(key_addr));
}
inline void unmask(uint8_t key_addr) {
KeyboardHardware.unMaskKey(row(key_addr), col(key_addr));
}
} // namespace addr {
} // namespace kaleidoscope {

@ -0,0 +1,396 @@
/* -*- mode: c++ -*-
* Kaleidoscope-Qukeys -- Assign two keycodes to a single key
* Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
*/
#include <Kaleidoscope.h>
#include <Kaleidoscope-Qukeys.h>
#include <kaleidoscope/hid.h>
#include <MultiReport/Keyboard.h>
#include <Kaleidoscope-Ranges.h>
#ifdef ARDUINO_VIRTUAL
#define debug_print(...) printf(__VA_ARGS__)
#else
#define debug_print(...)
#endif
namespace kaleidoscope {
namespace plugin {
inline
bool isDualUse(Key k) {
if (k.raw < ranges::DU_FIRST || k.raw > ranges::DU_LAST)
return false;
return true;
}
inline
bool isDualUse(byte key_addr) {
byte row = addr::row(key_addr);
byte col = addr::col(key_addr);
Key k = Layer.lookup(row, col);
return isDualUse(k);
}
Key getDualUsePrimaryKey(Key k) {
if (k.raw >= ranges::DUM_FIRST && k.raw <= ranges::DUM_LAST) {
k.raw -= ranges::DUM_FIRST;
k.flags = 0;
} else if (k.raw >= ranges::DUL_FIRST && k.raw <= ranges::DUL_LAST) {
k.raw -= ranges::DUL_FIRST;
k.flags = 0;
}
return k;
}
Key getDualUseAlternateKey(Key k) {
if (k.raw >= ranges::DUM_FIRST && k.raw <= ranges::DUM_LAST) {
k.raw -= ranges::DUM_FIRST;
k.raw = (k.raw >> 8) + Key_LeftControl.keyCode;
} else if (k.raw >= ranges::DUL_FIRST && k.raw <= ranges::DUL_LAST) {
k.raw -= ranges::DUL_FIRST;
byte layer = k.flags;
// Should be `ShiftToLayer(layer)`, but that gives "narrowing conversion"
// warnings that I can't figure out how to resolve
k.keyCode = layer + LAYER_SHIFT_OFFSET;
k.flags = KEY_FLAGS | SYNTHETIC | SWITCH_TO_KEYMAP;
}
return k;
}
Qukey::Qukey(int8_t layer, byte row, byte col, Key alt_keycode) {
this->layer = layer;
this->addr = addr::addr(row, col);
this->alt_keycode = alt_keycode;
}
Qukey * Qukeys::qukeys;
uint8_t Qukeys::qukeys_count = 0;
bool Qukeys::active_ = true;
uint16_t Qukeys::time_limit_ = 250;
uint8_t Qukeys::release_delay_ = 0;
QueueItem Qukeys::key_queue_[] = {};
uint8_t Qukeys::key_queue_length_ = 0;
byte Qukeys::qukey_state_[] = {};
bool Qukeys::flushing_queue_ = false;
constexpr uint16_t QUKEYS_RELEASE_DELAY_OFFSET = 4096;
// Empty constructor; nothing is stored at the instance level
Qukeys::Qukeys(void) {}
int8_t Qukeys::lookupQukey(uint8_t key_addr) {
if (key_addr == QUKEY_UNKNOWN_ADDR) {
return QUKEY_NOT_FOUND;
}
for (int8_t i = 0; i < qukeys_count; i++) {
if (qukeys[i].addr == key_addr) {
byte row = addr::row(key_addr);
byte col = addr::col(key_addr);
if ((qukeys[i].layer == QUKEY_ALL_LAYERS) ||
(qukeys[i].layer == Layer.lookupActiveLayer(row, col))) {
return i;
}
}
}
return QUKEY_NOT_FOUND;
}
void Qukeys::enqueue(uint8_t key_addr) {
if (key_queue_length_ == QUKEYS_QUEUE_MAX) {
setQukeyState(key_queue_[0].addr, QUKEY_STATE_PRIMARY);
flushKey(QUKEY_STATE_PRIMARY, IS_PRESSED | WAS_PRESSED);
flushQueue();
}
// default to alternate state to stop keys being flushed from the queue before the grace
// period timeout
setQukeyState(key_addr, QUKEY_STATE_ALTERNATE);
key_queue_[key_queue_length_].addr = key_addr;
key_queue_[key_queue_length_].start_time = millis();
key_queue_length_++;
addr::mask(key_addr);
}
int8_t Qukeys::searchQueue(uint8_t key_addr) {
for (int8_t i = 0; i < key_queue_length_; i++) {
if (key_queue_[i].addr == key_addr)
return i;
}
return QUKEY_NOT_FOUND;
}
// flush a single entry from the head of the queue
bool Qukeys::flushKey(bool qukey_state, uint8_t keyswitch_state) {
addr::unmask(key_queue_[0].addr);
int8_t qukey_index = lookupQukey(key_queue_[0].addr);
bool is_qukey = (qukey_index != QUKEY_NOT_FOUND);
byte row = addr::row(key_queue_[0].addr);
byte col = addr::col(key_queue_[0].addr);
bool is_dual_use = isDualUse(Layer.lookup(row, col));
Key keycode = Key_NoKey;
if (is_qukey || is_dual_use) {
if (qukey_state == QUKEY_STATE_PRIMARY &&
getQukeyState(key_queue_[0].addr) == QUKEY_STATE_ALTERNATE) {
// If there's a release delay in effect, and there's at least one key after it in
// the queue, delay this key's release event:
if (release_delay_ > 0 && key_queue_length_ > 1) {
key_queue_[0].start_time = millis() + QUKEYS_RELEASE_DELAY_OFFSET;
return false;
}
}
setQukeyState(key_queue_[0].addr, qukey_state);
if (qukey_state == QUKEY_STATE_ALTERNATE) {
if (is_dual_use) {
keycode = getDualUseAlternateKey(keycode);
} else { // is_qukey
keycode = qukeys[qukey_index].alt_keycode;
}
}
}
// Before calling handleKeyswitchEvent() below, make sure Qukeys knows not to handle
// these events:
flushing_queue_ = true;
// Since we're in the middle of the key scan, we don't necessarily
// have a full HID report, and we don't want to accidentally turn
// off keys that the scan hasn't reached yet, so we force the
// current report to be the same as the previous one, then proceed
HID_KeyboardReport_Data_t hid_report;
// First, save the current report
memcpy(hid_report.allkeys, Keyboard.keyReport.allkeys, sizeof(hid_report));
// Next, copy the old report
memcpy(Keyboard.keyReport.allkeys, Keyboard.lastKeyReport.allkeys, sizeof(Keyboard.keyReport));
// Instead of just calling pressKey here, we start processing the
// key again, as if it was just pressed, and mark it as injected, so
// we can ignore it and don't start an infinite loop. It would be
// nice if we could use key_state to also indicate which plugin
// injected the key.
handleKeyswitchEvent(keycode, row, col, IS_PRESSED);
// Now we send the report (if there were any changes)
hid::sendKeyboardReport();
// Next, we restore the current state of the report
memcpy(Keyboard.keyReport.allkeys, hid_report.allkeys, sizeof(hid_report));
// Last, if the key is still down, add its code back in
if (keyswitch_state & IS_PRESSED)
handleKeyswitchEvent(keycode, row, col, IS_PRESSED | WAS_PRESSED);
// Now that we're done sending the report(s), Qukeys can process events again:
flushing_queue_ = false;
// Shift the queue, so key_queue[0] is always the first key that gets processed
for (byte i = 0; i < key_queue_length_; i++) {
key_queue_[i] = key_queue_[i + 1];
}
key_queue_length_--;
return true;
}
// flushQueue() is called when a key that's in the key_queue is
// released. This means that all the keys ahead of it in the queue are
// still being held, so first we flush them, then we flush the
// released key (with different parameters).
void Qukeys::flushQueue(int8_t index) {
if (index == QUKEY_NOT_FOUND)
return;
for (int8_t i = 0; i < index; i++) {
if (key_queue_length_ == 0)
return;
flushKey(QUKEY_STATE_ALTERNATE, IS_PRESSED | WAS_PRESSED);
}
if (isQukey(key_queue_[0].addr)) {
flushKey(QUKEY_STATE_PRIMARY, IS_PRESSED | WAS_PRESSED);
} else {
flushKey(QUKEY_STATE_PRIMARY, WAS_PRESSED);
}
}
// Flush all the non-qukey keys from the front of the queue
void Qukeys::flushQueue() {
// flush keys until we find a qukey:
while (key_queue_length_ > 0 && !isQukey(key_queue_[0].addr)) {
if (flushKey(QUKEY_STATE_PRIMARY, IS_PRESSED | WAS_PRESSED) == false)
break;
}
}
inline
bool Qukeys::isQukey(uint8_t addr) {
return (isDualUse(addr) || lookupQukey(addr) != QUKEY_NOT_FOUND);
}
EventHandlerResult Qukeys::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state) {
// If key_addr is not a physical key, ignore it; some other plugin injected it
if (row >= ROWS || col >= COLS || (key_state & INJECTED) != 0)
return EventHandlerResult::OK;
// If Qukeys is turned off, continue to next plugin
if (!active_) {
mapped_key = getDualUsePrimaryKey(mapped_key);
return EventHandlerResult::OK;
}
// get key addr & qukey (if any)
uint8_t key_addr = addr::addr(row, col);
int8_t qukey_index = lookupQukey(key_addr);
// If the key was injected (from the queue being flushed)
if (flushing_queue_) {
// If it's a DualUse key, we still need to update its keycode
if (isDualUse(mapped_key)) {
if (getQukeyState(key_addr) == QUKEY_STATE_ALTERNATE) {
mapped_key = getDualUseAlternateKey(mapped_key);
} else {
mapped_key = getDualUsePrimaryKey(mapped_key);
}
}
// ...otherwise, just continue to the next plugin
return EventHandlerResult::OK;
}
// If the key isn't active, and didn't just toggle off, continue to next plugin
if (!keyIsPressed(key_state) && !keyWasPressed(key_state)) {
mapped_key = getDualUsePrimaryKey(mapped_key);
return EventHandlerResult::OK;
}
// If the key was just pressed:
if (keyToggledOn(key_state)) {
// If the queue is empty and the key isn't a qukey, proceed:
if (key_queue_length_ == 0 &&
! isDualUse(mapped_key) &&
qukey_index == QUKEY_NOT_FOUND) {
return EventHandlerResult::OK;
}
// Otherwise, queue the key and stop processing:
enqueue(key_addr);
// flushQueue() has already handled this key release
return EventHandlerResult::EVENT_CONSUMED;
}
// In all other cases, we need to know if the key is queued already
int8_t queue_index = searchQueue(key_addr);
// If the key was just released:
if (keyToggledOff(key_state)) {
// If the key isn't in the key_queue, proceed
if (queue_index == QUKEY_NOT_FOUND) {
// If a qukey was released while in its alternate state, change its keycode
if (isDualUse(mapped_key)) {
if (getQukeyState(key_addr) == QUKEY_STATE_ALTERNATE) {
mapped_key = getDualUseAlternateKey(mapped_key);
} else {
mapped_key = getDualUsePrimaryKey(mapped_key);
}
} else if (qukey_index != QUKEY_NOT_FOUND) {
if (getQukeyState(key_addr) == QUKEY_STATE_ALTERNATE) {
mapped_key = qukeys[qukey_index].alt_keycode;
}
}
return EventHandlerResult::OK;
}
flushQueue(queue_index);
flushQueue();
mapped_key = getDualUsePrimaryKey(mapped_key);
return EventHandlerResult::OK;
}
// Otherwise, the key is still pressed
// If the key is not a qukey:
if (qukey_index == QUKEY_NOT_FOUND &&
! isDualUse(mapped_key)) {
// If the key was pressed before the keys in the queue, proceed:
if (queue_index == QUKEY_NOT_FOUND) {
return EventHandlerResult::OK;
} else {
// suppress this keypress; it's still in the queue
return EventHandlerResult::EVENT_CONSUMED;
}
}
// If the qukey is not in the queue, check its state
if (queue_index == QUKEY_NOT_FOUND) {
if (getQukeyState(key_addr) == QUKEY_STATE_ALTERNATE) {
if (isDualUse(mapped_key)) {
mapped_key = getDualUseAlternateKey(mapped_key);
} else {
mapped_key = qukeys[qukey_index].alt_keycode;
}
} else { // qukey_state == QUKEY_STATE_PRIMARY
mapped_key = getDualUsePrimaryKey(mapped_key);
}
return EventHandlerResult::OK;
}
// else state is undetermined; block. I could check timeouts here,
// but I'd rather do that in the pre-report hook
return EventHandlerResult::EVENT_CONSUMED;
}
EventHandlerResult Qukeys::beforeReportingState() {
uint16_t current_time = millis();
if (release_delay_ > 0 && key_queue_length_ > 0) {
int16_t diff_time = key_queue_[0].start_time - current_time;
if (diff_time > 0) {
int16_t delay_window = QUKEYS_RELEASE_DELAY_OFFSET - release_delay_;
if (diff_time < delay_window) {
setQukeyState(key_queue_[0].addr, QUKEY_STATE_PRIMARY);
flushKey(QUKEY_STATE_PRIMARY, WAS_PRESSED);
flushQueue();
}
return EventHandlerResult::OK;
}
}
// If the qukey has been held longer than the time limit, set its
// state to the alternate keycode and add it to the report
while (key_queue_length_ > 0) {
if ((current_time - key_queue_[0].start_time) > time_limit_) {
flushKey(QUKEY_STATE_ALTERNATE, IS_PRESSED | WAS_PRESSED);
flushQueue();
} else {
break;
}
}
return EventHandlerResult::OK;
}
EventHandlerResult Qukeys::onSetup() {
// initializing the key_queue seems unnecessary, actually
for (int8_t i = 0; i < QUKEYS_QUEUE_MAX; i++) {
key_queue_[i].addr = QUKEY_UNKNOWN_ADDR;
key_queue_[i].start_time = 0;
}
key_queue_length_ = 0;
return EventHandlerResult::OK;
}
} // namespace plugin {
} // namespace kaleidoscope {
kaleidoscope::plugin::Qukeys Qukeys;

@ -0,0 +1,148 @@
/* -*- mode: c++ -*-
* Kaleidoscope-Qukeys -- Assign two keycodes to a single key
* Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Kaleidoscope.h>
#include <kaleidoscope/addr.h>
#include <Kaleidoscope-Ranges.h>
// Maximum length of the pending queue
#define QUKEYS_QUEUE_MAX 8
// Total number of keys on the keyboard (assuming full grid)
#define TOTAL_KEYS ROWS * COLS
// Boolean values for storing qukey state
#define QUKEY_STATE_PRIMARY false
#define QUKEY_STATE_ALTERNATE true
// Initialization addr value for empty key_queue. This seems to be
// unnecessary, because we rely on keeping track of the lenght of the
// queue, anyway.
#define QUKEY_UNKNOWN_ADDR 0xFF
// Value to return when no match is found in Qukeys.dict. A successful
// match returns an index in the array, so this must be negative. Also
// used for failed search of the key_queue.
#define QUKEY_NOT_FOUND -1
// Wildcard value; this matches any layer
#define QUKEY_ALL_LAYERS -1
#define MT(mod, key) (Key) { \
.raw = kaleidoscope::ranges::DUM_FIRST + \
(((Key_ ## mod).keyCode - Key_LeftControl.keyCode) << 8) + (Key_ ## key).keyCode }
#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) (Key) { .raw = kaleidoscope::ranges::DUL_FIRST + (layer << 8) + (Key_ ## key).keyCode }
namespace kaleidoscope {
namespace plugin {
// Data structure for an individual qukey
struct Qukey {
public:
Qukey(void) {}
Qukey(int8_t layer, byte row, byte col, Key alt_keycode);
int8_t layer;
uint8_t addr;
Key alt_keycode;
};
// Data structure for an entry in the key_queue
struct QueueItem {
uint8_t addr; // keyswitch coordinates
uint16_t start_time; // time a queued key was pressed
};
// The plugin itself
class Qukeys : public kaleidoscope::Plugin {
// I could use a bitfield to get the state values, but then we'd
// have to check the key_queue (there are three states). Or use a
// second bitfield for the indeterminite state. Using a bitfield
// would enable storing the qukey list in PROGMEM, but I don't know
// if the added complexity is worth it.
public:
Qukeys(void);
static void activate(void) {
active_ = true;
}
static void deactivate(void) {
active_ = false;
}
static void toggle(void) {
active_ = !active_;
}
static void setTimeout(uint16_t time_limit) {
time_limit_ = time_limit;
}
static void setReleaseDelay(uint8_t release_delay) {
release_delay_ = release_delay;
}
static Qukey * qukeys;
static uint8_t qukeys_count;
EventHandlerResult onSetup();
EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state);
EventHandlerResult beforeReportingState();
private:
static bool active_;
static uint16_t time_limit_;
static uint8_t release_delay_;
static QueueItem key_queue_[QUKEYS_QUEUE_MAX];
static uint8_t key_queue_length_;
static bool flushing_queue_;
// Qukey state bitfield
static uint8_t qukey_state_[(TOTAL_KEYS) / 8 + ((TOTAL_KEYS) % 8 ? 1 : 0)];
static bool getQukeyState(uint8_t addr) {
return bitRead(qukey_state_[addr / 8], addr % 8);
}
static void setQukeyState(uint8_t addr, boolean qukey_state) {
bitWrite(qukey_state_[addr / 8], addr % 8, qukey_state);
}
static int8_t lookupQukey(uint8_t key_addr);
static void enqueue(uint8_t key_addr);
static int8_t searchQueue(uint8_t key_addr);
static bool flushKey(bool qukey_state, uint8_t keyswitch_state);
static void flushQueue(int8_t index);
static void flushQueue(void);
static bool isQukey(uint8_t addr);
};
} // namespace plugin {
// Backwards compatibility
typedef plugin::Qukey Qukey;
} // namespace kaleidoscope {
extern kaleidoscope::plugin::Qukeys Qukeys;
// macro for use in sketch file to simplify definition of qukeys
#define QUKEYS(qukey_defs...) { \
static kaleidoscope::plugin::Qukey qk_table[] = { qukey_defs }; \
Qukeys.qukeys = qk_table; \
Qukeys.qukeys_count = sizeof(qk_table) / sizeof(kaleidoscope::plugin::Qukey); \
}
Loading…
Cancel
Save