SHIFTING INTO BETTER KEYBOARD ERGONOMICS
       
       Over my holidays I developed a pain in my left pinkie. The cause of my
       wee finger's suffering was the stretch and hold maveuver needed to
       shift a character into the uppercase. I'd developed a similar
       repetitive strain injury a few weeks back, and a small change in my
       keyboard layout had abated the injury for some time. But owing to the
       feversish amount of writing I did over the course of 10 days the
       injury returned. Now the pain was much worse and it was happening
       during a time that was supposed to be unhindered, uninterupted
       leisure. Unacceptable! I took a deep dive into my QMK layout. I would
       revise my placement of the shift key and keep the injury from
       developing again and again much worse.
       
       I use the Reviung41 keyboard, designed by gtips and bought as a kit
       from Boardsource. The keyboard has 41 keys, five of which are
       available in the thumb cluster. Ideally I would move my shift key into
       the thumb cluster. But the area is packed! Four of the five keys are
       mapped to tap and hold actions. For instance: holding the return or
       space keys activates one of two layers; Tapping the control key
       inserts a parenthesis. At the time it didn't seem like I had a key to
       spare. (I'm still not sure it does, though that may change).
       
       
       OSM shift key as mod key tap
       ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       The first solution I attempted was to add a one shot modifier shift
       key as a tap function for the right most key in the thumb
       cluster. This solution failed. QMK only supports basic keycodes (which
       a OSM key is not!) for the tap/hold feature.
       
       
       OSM shift key on a layer
       ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       Next I tried and succeeded at putting the OSM key into a layer that
       could be turned on my holding the e key, `KC_E'. This solution worked
       well enough to allow my pinkie to heal. But it introduced two new
       problems. First: with any key combination involving that e key. For
       instance, my Emacs command `C-e' became unreliable. Sometimes the
       keyboard would shift into the OSM layer instead of sending `C-e'. My
       fingers weren't fast enough. QMK has `#define' statements that can be
       used to hone in the tap/hold logic with variables like `TAPPING_TERM',
       `PERMISSIVE_HOLD', `HOLD_ON_OTHER_KEY_PRESS'. I either couldn't find
       the right value or these were insufficient for my typing
       style. Second: I now had to type three keys to shift a single
       character. No good!
       
       
       Leader key reverse shifting
       ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       I liked the OSM approach and thought it could work, if refined. My
       next iteration of this solution was trigger the OSM shift using a
       leader key combination. However, I couldn't find a way to send the OSM
       shift key as a combo. I suspect there is a function or clever way to
       send it, but I gave up and moved on before finding an answer. What I
       took with me was the leader key feature. I liked the reliability of
       the feature. Plus, moving my shifting into a leader key combo would
       avoid the unreliability of the e's tap/hold functionality. I could
       turn that key back to just being an e!
       
       The leader key was set as the hold function of the left most key in
       the thumb cluster. In my keymap it looked like this: `LALT_T(KC_O)',
       alt on hold, "o" on tap. For some reason I didn't use QMK's default
       keycode for the leader key, `QMK_LEAD'. So I had to add a bit of code
       to make `KC_O' trigger the leader feature.
       
       ,----
       | bool process_record_user(uint16_t keycode, keyrecord_t *record) {
       |   switch (keycode) {
       |   case LALT_T(KC_O):
       |     if (record->tap.count && record->event.pressed) {
       |       // Can't send leader key as a basic keycode, so this hack triggers
       |       // the start of the leader key experience with an API call.
       |       leader_start();
       |       return false;
       |     }
       |     break;
       |   }
       |   return true;
       | }     
       `----
       
       As I mentioned, I tried to get an OSM shift key as part of a leader
       key combination but failed. So instead I came up with a very crazy
       solution, which is most simply stated as "shifting in reverse". In
       other words: the key is first typed unshifted, followed by a leader
       key combination, and then transformed into its shifted state. The
       outcome is produced by a series of clever hacks around the
       `process_record_user' function and a conversion of QMK's keycode to a
       shifted ascii character. Here's the code:
       
       ,----
       | // Place this in rules.mk
       | LEADER_ENABLE = yes
       `----
       
       ,----
       | uint16_t last_keycode = KC_NO;
       | bool is_leader = false;
       | 
       | char keycode_to_char(uint16_t keycode) {
       |   if (keycode >= KC_A && keycode <= KC_Z) {
       |     return 'A' + (keycode - KC_A);
       |   } 
       |   return 0;
       | }
       | 
       | bool process_record_user(uint16_t keycode, keyrecord_t *record) {
       |   switch (keycode) {
       |   case LALT_T(KC_O):
       |     if (record->tap.count && record->event.pressed) {
       |       leader_start();
       |       return false;        
       |     }
       |     break;
       |   }
       |   if (!is_leader)
       |     last_keycode = keycode;
       |   return true;
       | }
       | 
       | void leader_start_user(void) {
       |   is_leader = true;
       | }
       | 
       | void leader_end_user(void) {
       |   // I chose KC_H because it sits under my index finger.
       |   // But any key could be used as the leader combo.
       |   if (leader_sequence_one_key(KC_H)) {
       |     SEND_STRING(SS_TAP(X_BSPC));
       |     char kc = keycode_to_char(last_keycode); 
       |     send_char(kc);
       |   } 
       |   is_leader = false;
       | }
       `----
       
       The way it works is weird but makes sense. The last keycode is
       maintained inside the `last_keycode' variable. Importantly, this key
       is not updated when the leader feature (tracked using `is_leader') is
       enabled. That's to ignore any keys involved in the leader key
       sequence. (I suppose another approach would be to push and pop keys
       off an array, but I don't know how to do that in C). Anyways, when the
       correct leader key sequence is pressed the shifting in reverse
       occurs. First, a backspace character is typed out, removing the
       unshifted key. Then `last_keycode' is converted to its shifted ASCII
       character. Finally, that character is tapped out by the keyboard.
       
       This solution I found entirely unintuitive. I also thought it a funny,
       obscure way of doing a shift action. I was happy that I implemented it
       using my limited understanding of QMK and C. But it really was not
       that much of an improvement over my previous solution. There were
       fewer errors owing to the hold function of the e key, but new errors
       of a different kind. I had to execute the leader key sequence within
       the limited term window. And I had to wait for that window to elapes
       before the key would be shifted. So it was slow. Slow, unintuitive,
       obscure, and weird.
       
       
       Shifting via tap dance
       ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       I reflected on other approaches. I'd heard of QMK's tap dance feature,
       which lets you tap a key multiple times to execute a action. I thought
       this interesting, but imagined issues when typing out a word with the
       same character repeated in sequence.
       
       
       Shifting with key chords
       ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       Where i eventually landed was with chording. With chording, two or
       more keys can be held together at the same time to execute an
       action. I decided to try a chording solution to my shifting
       woes. Basically, I would create chords for each key that I want to tap
       out shifted. The chords would share a root key, which would help me
       easily remember and execute the chords. In the simplest form, this
       could mean a group of chords, where each chord is Z and a key from
       A-Y. To shift an arbitrarily selected key, I hold down Z and the
       selected key. Though simple (and with an obvious flaw: Z can't be
       shifted this way), I decided the ergonomics of this solution still
       subpar. I'd have to curl my pinky to reach Z. So I iterated, deciding
       to use two groups of chords: one for keys below my left hand, one for
       the keys below my right hand. The root key for each group would be on
       the other side of the keyboard. Practically, this means that to shift
       an arbitrary key on the right side of the keyboard I hold down "u"
       (located on the left side of the keyboard) plus that arbitrary key. To
       shift an arbitrary key on the left side of the keyboard I hold down
       "h" (located on the right side of the keyboard) plus that arbitrary
       key.
       
       My solution worked great, with one easily predictable issue: there is
       a single overlap of keys in my chords: "u" and "h". QMK can't
       distingush between a chord where "U" is the root and a key where "h"
       is the root. In otherwords:
       
       ,----
       | const uint16_t PROGMEM shifted_h[] = {KC_U, KC_H, COMBO_END};
       | const uint16_t PROGMEM shifted_u[] = {KC_H, KC_U, COMBO_END};
       `----
       
       The two combos above are essentially the same chord. After all, the
       keys of a chord can be written in any order and the result will always
       be the same. So with this solution I am without an easily shifted
       U. But I've accepted this flaw. For after typing this whole article up
       using this new shifting technique I have to say that I quite enjoy the
       chording feeling!
       
       Included at the end of this entry is my full config for the chording
       feature. Note that this is for a dvorak layout. If you use a QWERTY
       keyboard you'll have to adjust the groups quite a bit. While I would
       have liked for the implementation to have the chords programmatically
       cerated I couldn't figure out how to do that. So it's all static. But
       I was at least able to write it out using Emacs macros that saved me
       many valuable keystrokes! :)
       
       
       Where to from here
       ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       I am very happy with the results of my diversion into better keyboard
       shifting ergonomics. My end result isn't perfect--fine tuning of the
       chord typing term remains to avoid erroneous shifts. But my pinkie
       feels happy and unstarined. Plus, this exercise was a fun way to learn
       about the leader key and chording features of QMK. I definitely contin
       to refine how I send shifted keycodes. Already I am learning about
       other ways of controling shift, like using a 'next sentence'
       macro. The refinement of a keyboard is endless.
       
       
       Chording setup for DVORAK keyboard
       ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       ,----
       | // Place this in rules.mk
       | COMBO_ENABLE = yes
       `----
       
       ,----
       | // Combos for shifting letters on right side of keyboard
       | const uint16_t PROGMEM shifted_f[] = {KC_U, KC_F, COMBO_END};
       | const uint16_t PROGMEM shifted_g[] = {KC_U, KC_G, COMBO_END};
       | const uint16_t PROGMEM shifted_c[] = {KC_U, KC_C, COMBO_END};
       | const uint16_t PROGMEM shifted_r[] = {KC_U, KC_R, COMBO_END};
       | const uint16_t PROGMEM shifted_l[] = {KC_U, KC_L, COMBO_END};
       | const uint16_t PROGMEM shifted_d[] = {KC_U, KC_D, COMBO_END};
       | const uint16_t PROGMEM shifted_h[] = {KC_U, KC_H, COMBO_END};
       | const uint16_t PROGMEM shifted_t[] = {KC_U, KC_T, COMBO_END};
       | const uint16_t PROGMEM shifted_n[] = {KC_U, KC_N, COMBO_END};
       | const uint16_t PROGMEM shifted_s[] = {KC_U, KC_S, COMBO_END};
       | const uint16_t PROGMEM shifted_b[] = {KC_U, KC_B, COMBO_END};
       | const uint16_t PROGMEM shifted_m[] = {KC_U, KC_M, COMBO_END};
       | const uint16_t PROGMEM shifted_w[] = {KC_U, KC_W, COMBO_END};
       | const uint16_t PROGMEM shifted_v[] = {KC_U, KC_V, COMBO_END};
       | const uint16_t PROGMEM shifted_z[] = {KC_U, KC_Z, COMBO_END};
       | // Combos for shifting letter son left side of keyboard
       | const uint16_t PROGMEM shifted_p[] = {KC_H, KC_P, COMBO_END};
       | const uint16_t PROGMEM shifted_y[] = {KC_H, KC_Y, COMBO_END};
       | const uint16_t PROGMEM shifted_a[] = {KC_H, KC_A, COMBO_END};
       | const uint16_t PROGMEM shifted_o[] = {KC_H, KC_O, COMBO_END};
       | const uint16_t PROGMEM shifted_e[] = {KC_H, KC_E, COMBO_END};
       | const uint16_t PROGMEM shifted_u[] = {KC_H, KC_U, COMBO_END};
       | const uint16_t PROGMEM shifted_i[] = {KC_H, KC_I, COMBO_END};
       | const uint16_t PROGMEM shifted_q[] = {KC_H, KC_Q, COMBO_END};
       | const uint16_t PROGMEM shifted_j[] = {KC_H, KC_J, COMBO_END};
       | const uint16_t PROGMEM shifted_k[] = {KC_H, KC_K, COMBO_END};
       | const uint16_t PROGMEM shifted_x[] = {KC_H, KC_X, COMBO_END};
       | // The action associated with each chord.
       | combo_t key_combos[] = {
       |   COMBO(shifted_p, LSFT(KC_P)),
       |   COMBO(shifted_y, LSFT(KC_Y)),
       |   COMBO(shifted_a, LSFT(KC_A)),
       |   COMBO(shifted_o, LSFT(KC_O)),
       |   COMBO(shifted_e, LSFT(KC_E)),
       |   COMBO(shifted_u, LSFT(KC_U)),
       |   COMBO(shifted_i, LSFT(KC_I)),
       |   COMBO(shifted_q, LSFT(KC_Q)),
       |   COMBO(shifted_j, LSFT(KC_J)),
       |   COMBO(shifted_k, LSFT(KC_K)),
       |   COMBO(shifted_x, LSFT(KC_X)),
       |   COMBO(shifted_f, LSFT(KC_F)),
       |   COMBO(shifted_g, LSFT(KC_G)),
       |   COMBO(shifted_c, LSFT(KC_C)),
       |   COMBO(shifted_r, LSFT(KC_R)),
       |   COMBO(shifted_l, LSFT(KC_L)),
       |   COMBO(shifted_d, LSFT(KC_D)),
       |   COMBO(shifted_h, LSFT(KC_H)),
       |   COMBO(shifted_t, LSFT(KC_T)),
       |   COMBO(shifted_n, LSFT(KC_N)),
       |   COMBO(shifted_s, LSFT(KC_S)),
       |   COMBO(shifted_b, LSFT(KC_B)),
       |   COMBO(shifted_m, LSFT(KC_M)),
       |   COMBO(shifted_w, LSFT(KC_W)),
       |   COMBO(shifted_v, LSFT(KC_V)),
       |   COMBO(shifted_z, LSFT(KC_Z)),
       | };
       `----
       
       
       References
       ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       - 
       - 
       - 
       - 
       - 
       - 
       -