diff --git a/.astylerc b/.astylerc new file mode 100644 index 00000000..7ced720e --- /dev/null +++ b/.astylerc @@ -0,0 +1,6 @@ +--style=google +--unpad-paren +--pad-header +--pad-oper +--indent-classes +--indent=spaces=2 diff --git a/README.md b/README.md index bac8b772..7b0d90be 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,15 @@ likely to generate errors and out-of-order events. [example](https://github.com/gedankenlab/Kaleidoscope-Qukeys/blob/master/examples/Qukeys/Qukeys.ino) for a way to turn `Qukeys` on and off, using Kaleidoscope-Macros +### DualUse key definitions + +In addition to normal `Qukeys` described above, Kaleidoscope-Qukeys also treats +DualUse keys in the keymap as `Qukeys`. See [the Kaleidoscope-DualUse +documentation](https://github.com/keyboardio/Kaleidoscope-DualUse#keymap-markup) +for a thorough description of how to define DualUse keys. This makes `Qukeys` a +drop-in replacement for the `DualUse` plugin, without the need to edit the +keymap. + ## Design & Implementation diff --git a/examples/Qukeys/Qukeys.ino b/examples/Qukeys/Qukeys.ino index 862fabb0..cfc33638 100644 --- a/examples/Qukeys/Qukeys.ino +++ b/examples/Qukeys/Qukeys.ino @@ -10,21 +10,41 @@ enum { MACRO_TOGGLE_QUKEYS }; 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_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_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, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote, - Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, + 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, - ___), + 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* @@ -47,7 +67,8 @@ void setup() { kaleidoscope::Qukey(0, 2, 1, Key_LeftGui), // A/cmd kaleidoscope::Qukey(0, 2, 2, Key_LeftAlt), // S/alt kaleidoscope::Qukey(0, 2, 3, Key_LeftControl), // D/ctrl - kaleidoscope::Qukey(0, 2, 4, Key_LeftShift) // F/shift + kaleidoscope::Qukey(0, 2, 4, Key_LeftShift), // F/shift + kaleidoscope::Qukey(0, 3, 6, ShiftToLayer(1)) // Q/layer-shift (on `fn`) ) Qukeys.setTimeout(200); diff --git a/src/Kaleidoscope/Qukeys.cpp b/src/Kaleidoscope/Qukeys.cpp index 60a5dde8..1c652b74 100644 --- a/src/Kaleidoscope/Qukeys.cpp +++ b/src/Kaleidoscope/Qukeys.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #ifdef ARDUINO_VIRTUAL #define debug_print(...) printf(__VA_ARGS__) @@ -30,6 +32,39 @@ namespace kaleidoscope { +bool isDualUse(Key k) { + if (k.raw < ranges::DU_FIRST || k.raw > ranges::DU_LAST) + return false; + return true; +} + +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); @@ -89,14 +124,20 @@ int8_t Qukeys::searchQueue(uint8_t key_addr) { void Qukeys::flushKey(bool qukey_state, uint8_t keyswitch_state) { addr::unmask(key_queue_[0].addr); int8_t qukey_index = lookupQukey(key_queue_[0].addr); - if (qukey_index != QUKEY_NOT_FOUND) { - setQukeyState(key_queue_[0].addr, qukey_state); - } + 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 (qukey_state == QUKEY_STATE_ALTERNATE && qukey_index != QUKEY_NOT_FOUND) { - keycode = qukeys[qukey_index].alt_keycode; + if (is_qukey || is_dual_use) { + 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 @@ -125,7 +166,7 @@ void Qukeys::flushKey(bool qukey_state, uint8_t keyswitch_state) { memcpy(Keyboard.keyReport.allkeys, hid_report.allkeys, sizeof(hid_report)); // Last, if the key is still down, add its code back in - if (! keyToggledOn(keyswitch_state)) + 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: @@ -166,28 +207,42 @@ 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; + return getDualUsePrimaryKey(mapped_key); + + // 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), continue to next plugin - if (flushing_queue_) + // 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) { + return getDualUseAlternateKey(mapped_key); + } else { + return getDualUsePrimaryKey(mapped_key); + } + } + // ...otherwise, just continue to the next plugin 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 = lookupQukey(key_addr); + return getDualUsePrimaryKey(mapped_key); // 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 && - qukey_index == QUKEY_NOT_FOUND) + ! isDualUse(mapped_key) && + qukey_index == QUKEY_NOT_FOUND) { return mapped_key; + } + // Otherwise, queue the key and stop processing: enqueue(key_addr); + // flushQueue() has already handled this key release return Key_NoKey; } @@ -199,21 +254,25 @@ Key Qukeys::keyScanHook(Key mapped_key, byte row, byte col, uint8_t 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 (qukey_index != QUKEY_NOT_FOUND && - getQukeyState(key_addr) == QUKEY_STATE_ALTERNATE) { - return qukeys[qukey_index].alt_keycode; + if (isDualUse(mapped_key)) { + if (getQukeyState(key_addr) == QUKEY_STATE_ALTERNATE) + return getDualUseAlternateKey(mapped_key); + return getDualUsePrimaryKey(mapped_key); + } else if (qukey_index != QUKEY_NOT_FOUND) { + if (getQukeyState(key_addr) == QUKEY_STATE_ALTERNATE) + return qukeys[qukey_index].alt_keycode; + return mapped_key; } - return mapped_key; } flushQueue(queue_index); - // flushQueue() has already handled this key release return Key_NoKey; } // Otherwise, the key is still pressed // If the key is not a qukey: - if (qukey_index == QUKEY_NOT_FOUND) { + 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 mapped_key; @@ -226,9 +285,11 @@ Key Qukeys::keyScanHook(Key mapped_key, byte row, byte col, uint8_t key_state) { // 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)) + return getDualUseAlternateKey(mapped_key); return qukeys[qukey_index].alt_keycode; } else { // qukey_state == QUKEY_STATE_PRIMARY - return mapped_key; + return getDualUsePrimaryKey(mapped_key); } } // else state is undetermined; block. I could check timeouts here, @@ -241,12 +302,18 @@ void Qukeys::preReportHook(void) { // state to the alternate keycode and add it to the report uint16_t current_time = (uint16_t)millis(); while (key_queue_length_ > 0) { - if (lookupQukey(key_queue_[0].addr) == QUKEY_NOT_FOUND) { - flushKey(QUKEY_STATE_PRIMARY, IS_PRESSED | WAS_PRESSED); - } else if ((current_time - key_queue_[0].start_time) > time_limit_) { - flushKey(QUKEY_STATE_ALTERNATE, IS_PRESSED | WAS_PRESSED); + byte row = addr::row(key_queue_[0].addr); + byte col = addr::col(key_queue_[0].addr); + Key keycode = Layer.lookup(row, col); + bool is_dual_use = isDualUse(keycode); + if (lookupQukey(key_queue_[0].addr) != QUKEY_NOT_FOUND || is_dual_use) { + if ((current_time - key_queue_[0].start_time) > time_limit_) { + flushKey(QUKEY_STATE_ALTERNATE, IS_PRESSED | WAS_PRESSED); + } else { + break; + } } else { - break; + flushKey(QUKEY_STATE_PRIMARY, IS_PRESSED | WAS_PRESSED); } } } diff --git a/src/Kaleidoscope/Qukeys.h b/src/Kaleidoscope/Qukeys.h index 2994bdff..4d718d97 100644 --- a/src/Kaleidoscope/Qukeys.h +++ b/src/Kaleidoscope/Qukeys.h @@ -20,6 +20,7 @@ #include #include +#include // Maximum length of the pending queue #define QUKEYS_QUEUE_MAX 8 @@ -41,6 +42,16 @@ // 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 { // Data structure for an individual qukey