commit
b0e3db9328
@ -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…
Reference in new issue