Version 1 of the Qukeys implementation

I think everything is there. This probably doesn't even compile yet,
and it certainly hasn't been debugged.
pull/389/head
Michael Richters 7 years ago
parent 29c243eda5
commit 5bfe5c07eb

@ -1 +1,45 @@
// -*- mode: c++ -*-
// -*- mode: c++ -*-
#include <Kaleidoscope.h>
#include <Kaleidoscope-Qukeys.h>
const Key keymaps[][ROWS][COLS] PROGMEM = {
[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_skip, 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,
___),
};
// Define the Qukeys map
QUKEYS(
Qukey(QWERTY, 2, 1, Key_LeftShift),
Qukey(QWERTY, 2, 2, Key_LeftControl),
Qukey(QWERTY, 2, 3, Key_LeftAlt),
Qukey(QWERTY, 2, 4, Key_LeftGui)
)
void setup() {
// Use Qukeys
Kaleidoscope.use(&Qukeys);
Qukeys.init(qukey_list, qukey_count);
Kaleidoscope.setup();
}
void loop() {
Kaleidoscope.loop();
}

@ -28,8 +28,191 @@
namespace kaleidoscope {
// constructor
Qukeys::Qukeys(void) {
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;
this->state = QUKEY_STATE_UNDETERMINED;
}
Qukeys::Qukeys() {}
Qukeys::init(Qukey *qukeys, uint8_t qukeys_count) {
qukeys_ = qukeys;
qukeys_count_ = qukeys_count;
}
int8_t Qukeys::lookupQukey(uint8_t key_addr) {
if (key_addr == QUKEY_UNKNOWN_ADDR)
return QUKEY_NOT_FOUND;
for (int8_t i; i < qukeys_count_; i++) {
Qukey qukey = qukeys_[i];
if (qukey.addr == key_addr) {
byte row = addr::row(key_addr);
byte col = addr::col(key_addr);
if ((qukey.layer == QUKEY_ALL_LAYERS) ||
(qukey.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) {
flushKey(QUKEY_STATE_PRIMARY);
}
key_queue_[key_queue_length_].addr = key_addr;
key_queue_[key_queue_length_].flush_time = millis() + time_limit_;
key_queue_length_++;
}
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 -1;
}
// flush a single entry from the head of the queue
void Qukeys::flushKey(int8_t state) {
int8_t qukey_index = Qukeys.lookupQukey(key_addr);
if (qukey_index != QUKEY_NOT_FOUND) {
qukeys_[qukey_index].state = state;
}
byte row = addr::row(key_queue[0].addr);
byte col = addr::col(key_queue[0].addr);
Key keycode = Key_NoKey;
if (state == QUKEY_STATE_ALTERNATE && qukey_index != QUKEY_NOT_FOUND) {
keycode = qukeys_[qukey_index].alt_keycode;
} else {
keycode = Layer.lookup(row, col);
}
// 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::copyPrevKeyboardReport();
// 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 | INJECTED);
// Now we send the report (if there were any changes)
hid::sendKeyboardReport();
// 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_--;
}
void Qukeys::flushQueue(int8_t state, int8_t index) {
for (int8_t i = 0; i <= index; i++) {
if (key_queue_length_ == 0)
break;
flushKey(state);
}
}
Key Qukeys::keyScanHook(Key mapped_key, byte row, byte col, uint8_t key_state) {
// If Qukeys is turned off, continue to next plugin
if (!active)
return mapped_key;
// If the key was injected (from the queue being flushed), continue to next plugin
if (key_state & INJECTED)
return mapped_key;
// If the key isn't active, and didn't just toggle off, continue to next plugin
if (!keyIsPressed(key_state) && !keyWasPressed(key_state))
return mapped_key;
// get key addr & qukey (if any)
uint8_t key_addr = addr::addr(row, col);
int8_t qukey_index = Qukeys.lookupQukey(key_addr);
// If the key was just pressed:
if (keyToggledOn(key_state)) {
// I think I may need to call maskKey() somewhere here, but I'm not sure
if (key_queue_length) {
enqueue(key_addr);
} else {
if (qukey_index == QUKEY_NOT_FOUND)
return mapped_key;
enqueue(key_addr);
}
}
// 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 (queue_index == QUKEY_NOT_FOUND)
return mapped_key;
flushQueue(QUKEY_STATE_ALTERNATE, queue_index);
return Key_NoKey;
}
// Otherwise, the key is still pressed
// If the key is not a qukey:
if (qukey_index == QUKEY_NOT_FOUND) {
// If the key was pressed before the keys in the queue:
if (queue_index == QUKEY_NOT_FOUND) {
return mapped_key;
} else {
// suppress this keypress; it's still in the queue
return Key_NoKey;
}
}
// qukeys that have already decided their keycode
if (qukeys_[qukey_index].state == QUKEY_STATE_PRIMARY)
return mapped_key;
if (qukeys_[qukey_index].state == QUKEY_STATE_ALTERNATE)
return qukeys_[qukey_index].alt_keycode;
// else state is undetermined; block. I could check timeouts here,
// but I'd rather do that in the pre-report hook
return Key_NoKey;
}
void preReportHook(void) {
// If the qukey has been held longer than the time limit, set its
// state to the alternate keycode and add it to the report
uint32_t current_time = millis();
for (int8_t i = 0; i < key_queue_length_; i++) {
if (current_time > key_queue_[i].flush_time) {
flushKey(QUKEY_STATE_ALTERNATE);
} else {
break;
}
}
}
void loopHook(bool post_clear) {
if (!post_clear)
return preReportHook();
}
void Qukeys::begin() {
// 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].flush_time = 0;
}
key_queue_length_ = 0;
Kaleidoscope.useEventHandlerHook(keyScanHook);
Kaleidoscope.useLoopHook(loopHook);
}
} // namespace kaleidoscope {
kaleidoscope::Qukeys Qukeys;

@ -19,19 +19,89 @@
#pragma once
#include <Kaleidoscope.h>
#include <addr.h>
// Maximum number of qukeys allowed ­ not actually used
#define QUKEYS_MAX 64
// Maximum length of the pending queue
#define QUKEYS_QUEUE_MAX 8
// Maybe it's better to use an enum for these state values?
#define QUKEY_STATE_UNDETERMINED 0
#define QUKEY_STATE_PRIMARY 1
#define QUKEY_STATE_ALTERNATE -1
// 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
namespace kaleidoscope {
class Qukeys : public KaleidoscopePlugin {
// 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;
int8_t state;
};
// Data structure for an entry in the key_queue
struct QueueItem {
uint8_t addr; // keyswitch coordinates
uint32_t flush_time; // time past which a qukey gets flushed
};
// The plugin itself
class Qukeys : public KaleidoscopePlugin {
// 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);
void begin(void) final;
static void begin(void) final;
static void activate(void) {
active = true;
}
static void deactivate(void) {
active = false;
}
static int8_t lookupQukey(uint8_t key_addr);
private:
}
static Qukey * qukeys_;
static uint8_t qukeys_count_;
static bool active_;
static uint16_t time_limit_;
static queue_item_t key_queue_[QUKEYS_QUEUE_MAX];
static uint8_t key_queue_length_;
static Key keyScanHook(Key mapped_key, byte row, byte col, uint8_t key_state);
static void preReportHook(void);
static void postReportHook(void) {}
static void loopHook(bool post_clear);
};
} // namespace kaleidoscope {
extern kaleidoscope::Qukeys Qukeys;
}
// macro for use in sketch file to simplify definition of qukeys
#define QUKEYS(qukey_defs...) \
static kaleidoscope::Qukey qukeys[] = { qukey_defs... }; \
uint8_t qukeys_count = sizeof(qukeys) / sizeof(kaleidoscope::Qukey); \
Qukeys.init(qukeys, qukeys_count);

Loading…
Cancel
Save