Skip to content

Instantly share code, notes, and snippets.

@bogorad
Created January 1, 2026 17:13
Show Gist options
  • Select an option

  • Save bogorad/68f34105640b8467c19e42db739fb9b8 to your computer and use it in GitHub Desktop.

Select an option

Save bogorad/68f34105640b8467c19e42db739fb9b8 to your computer and use it in GitHub Desktop.
My current Miryoku modification architecture

Chuck's Miryoku Customization

Complete documentation of all custom code and behaviors in this Miryoku setup.


Table of Contents

  1. Overview
  2. Language Switching System
  3. Special Key Behaviors
  4. Home Row Mods Configuration
  5. Custom Keycodes
  6. Layer Structure
  7. Combos
  8. Helper Functions
  9. Configuration Settings
  10. Debug Logging

Overview

This is a heavily customized Miryoku layout for a split Corne keyboard (crkbd) with:

  • Bilingual support (English/Russian) with automatic language switching
  • Chordal Hold for home row mods (migrated from Achordion)
  • Custom layer-tap behavior with Windows keyboard layout integration
  • Wayland-specific workarounds for button layer
  • Extensive debugging and logging capabilities

Key Technologies:

  • QMK Firmware (master branch, 2025-08+)
  • Chordal Hold (native QMK opposite-hands detection)
  • Flow Tap (typing streak detection)
  • Speculative Hold (eager mod application)
  • Windows AHK integration via F21/F22 signals

Language Switching System

Dual Language Architecture

The system manages TWO independent states:

  1. QMK Layers (firmware side):

    • U_BASE (0) - English Colemak-DH layout
    • U_EXTRA (1) - Russian QWERTY layout
  2. Windows Keyboard Layout (OS side):

    • Controlled by AutoHotKey via F21/F22 signals
    • F21 → English
    • F22 → Russian

Automatic Language Switching

When NUM/SYM/FUN Layers Activate:

From Russian:

U_EXTRA → Press NUM → layer_state_set_user() detects transition
                   → Sends F21 (temporary English)
                   → Numbers/symbols typed in English
       → Release NUM → Sends F22 (back to Russian)

Implementation:

layer_state_t layer_state_set_user(layer_state_t state) {
    // Switching TO num/sym/fun → English
    if ((highest == U_NUM || highest == U_SYM || highest == U_FUN) &&
        previous_layer == U_EXTRA) {
        send_layout_signal(KC_F21);  // Windows → English
    }

    // Switching FROM num/sym/fun → restore language
    if (previous_layer was NUM/SYM/FUN && highest != NUM/SYM/FUN) {
        if (permanent_english_switch) {
            // Win+Number triggered - stay in English
            state = (layer_state_t)1 << U_BASE;
            send_layout_signal(KC_F21);
        } else if (highest == U_EXTRA) {
            send_layout_signal(KC_F22);  // Back to Russian
        }
    }
}

Manual Language Switching

LANG_ENG / LANG_RUS keycodes:

  • switch_to_english() - F21 + layer_move(U_BASE)
  • switch_to_russian() - F22 + layer_move(U_EXTRA) (toggles if already Russian)

D+H combo → LANG_RUS (manual Russian switch)

Russian Layer Timeout

Auto-switch to English after 2 seconds of inactivity:

#define RUS_LAYER_TIMEOUT 2000  // milliseconds

void matrix_scan_user(void) {
    if (U_EXTRA == get_highest_layer(layer_state)) {
        if (RUS_LAYER_TIMEOUT < last_input_activity_elapsed()) {
            switch_to_english();
        }
    }
}

Why: Prevents getting stuck in Russian layer when switching to other apps.


Special Key Behaviors

1. Win+Number Permanent Switch

Behavior: Pressing Win+1 through Win+9/0 in Russian permanently switches to English.

Rationale: Window management shortcuts (Win+1 = switch to window 1) are a context change - you're switching to a different app, likely want to stay in English.

Implementation:

// In process_record_user():
if ((layer == U_NUM || layer == U_FUN) &&
    layer_state_cmp(layer_state, U_EXTRA) &&
    win_mods && (is_number || is_function)) {

    permanent_english_switch = true;  // Set flag
    // Actual switch happens in layer_state_set_user() after keypress
}

// In layer_state_set_user():
if (permanent_english_switch) {
    permanent_english_switch = false;
    state = (layer_state_t)1 << U_BASE;  // Permanent switch
    send_layout_signal(KC_F21);
}

Why the flag approach:

  • Can't call layer_move() during keypress - would deactivate layer immediately
  • Keycode would be read from wrong layer (Win+T instead of Win+6)
  • Flag defers the switch until after keypress completes

Affected keys:

  • Win+1,2,3,4,5,6,7,8,9,0 (window switching)
  • Win+F1-F24 (function shortcuts like Win+F4 = close window)

2. Ctrl+Enter Permanent Switch

Behavior: Pressing Ctrl+Enter (ONLY Ctrl, no other mods) in Russian permanently switches to English AND sends Ctrl+Enter.

Rationale: Ctrl+Enter is often "send message" or "submit" - after submitting, you likely want to stay in English.

Implementation:

if (get_highest_layer(layer_state) == U_EXTRA &&
    is_enter_key &&
    (mod_state & MOD_MASK_CTRL) &&
    !(mod_state & ~MOD_MASK_CTRL)) {

    switch_to_english();      // F21 + U_BASE
    need_to_restore_layer = false;
    wait_ms(20);              // Give AHK time
    tap_code(KC_ENT);         // Explicitly send Enter (Ctrl still held)
    return false;
}

Critical detail: Explicitly sends tap_code(KC_ENT) because the Enter key is a layer-tap that we intercept.

3. HRM Temporary English Switch

Behavior: Pressing any non-Shift modifier (Ctrl/Alt/Win) while in Russian temporarily switches to English.

Rationale: Shortcuts like Ctrl+C, Alt+Tab, Win+E are English-based, need English layout temporarily.

Implementation:

if (get_highest_layer(layer_state) == U_EXTRA && record->event.pressed) {
    uint8_t non_shift_mods = mod_state & ~MOD_MASK_SHIFT;

    if (non_shift_mods) {
        layer_move(U_BASE);      // Temporary switch
        need_to_restore_layer = true;
    }
}

// Restore when ALL non-Shift mods released:
if (!record->event.pressed && need_to_restore_layer) {
    uint8_t remaining_mods = get_mods() & ~MOD_MASK_SHIFT;

    if (remaining_mods == 0) {
        layer_move(U_EXTRA);
        send_layout_signal(KC_F22);  // Windows → Russian
        need_to_restore_layer = false;
    }
}

Why check all mods: Handles sequences like Ctrl+A+B correctly (doesn't restore until ALL mods released).

4. Custom Punctuation (Always English)

Keys: MY_COMMA, MY_DOT, MY_QUESTION, MY_SLASH

Behavior: These keys ALWAYS send punctuation in English, regardless of current layer.

Rationale: Programming, URLs, paths need consistent punctuation placement.

Implementation:

void send_in_english(char* msg) {
    uint8_t mod_state = get_mods();
    bool russian_active = (U_EXTRA == get_highest_layer(layer_state));

    clear_mods();
    if (russian_active) switch_to_english();

    wait_ms(50);
    SEND_STRING(msg);
    wait_ms(50);

    if (russian_active) switch_to_russian();
    set_mods(mod_state);
}

Used by:

  • MY_COMMA: Sends "," (or Shift+",")
  • MY_DOT: Sends "." (or Shift+".")
  • MY_QUESTION: Sends "?!"
  • MY_SLASH: Sends "/" (or Shift+"/")

5. Button Layer (Wayland Workaround)

Keys: BUT_A through BUT_Z

Behavior: Sends Ctrl+Alt+Win+A through Z.

Rationale: Wayland has race conditions with regular keybindings. This sends all modifiers explicitly with delays.

Implementation:

if (keycode >= BUT_A && keycode <= BUT_Z) {
    int new_keycode = KC_A + keycode - BUT_A;

    if (record->event.pressed) {
        clear_mods();
        register_code(KC_LCTL);
        wait_ms(10);
        register_code(KC_LALT);
        wait_ms(10);
        register_code(KC_LGUI);
        wait_ms(10);
        register_code(new_keycode);
    } else {
        unregister_code(new_keycode);
        wait_ms(10);
        unregister_code(KC_LGUI);
        wait_ms(10);
        unregister_code(KC_LALT);
        wait_ms(10);
        unregister_code(KC_LCTL);
    }
}

Activation: Z+/ combo → oneshot BUTTON layer

6. Vim Commands

VIM_SAVE: Sends Esc :w Enter (save file)

VIM_CMD: Sends Esc : (enter command mode)

Combos:

  • W+F → VIM_SAVE

Home Row Mods Configuration

QMK Settings

#define TAPPING_TERM 200              // Quick tap decision (was 175, increased for Chordal Hold)
#define CHORDAL_HOLD                  // Native opposite-hands detection
#define CHORDAL_HOLD_TIMEOUT 1000     // 1 second window for chords (matches Achordion default)
#define PERMISSIVE_HOLD               // Settle as hold on next key release
#define QUICK_TAP_TERM 0              // No auto-repeat on mod-taps
#define QUICK_TAP_TERM_PER_KEY        // Per-key configuration

Chordal Hold (Opposite Hands Detection)

Principle: Mod-tap activates as HOLD instantly when opposite hand presses a key, TAP when same hand presses a key.

Implementation:

bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t* tap_hold_record,
                      uint16_t other_keycode, keyrecord_t* other_record) {
    // Thumb keys always hold
    if (3 == tap_hold_record->event.key.row % (MATRIX_ROWS / 2)) {
        return true;
    }

    // ANY modifier + NUM/FUN layer → always HOLD
    if (IS_QK_MOD_TAP(tap_hold_keycode)) {
        uint8_t other_layer = 0;
        if (IS_QK_LAYER_TAP(other_keycode)) {
            other_layer = QK_LAYER_TAP_GET_LAYER(other_keycode);
        }
        if (other_layer == U_NUM || other_layer == U_FUN) {
            return true;  // Ctrl+5, Alt+F4, etc.
        }
    }

    // Opposite hands detection for split keyboard
    uint8_t tap_hand = tap_hold_record->event.key.row / (MATRIX_ROWS / 2);
    uint8_t other_hand = other_record->event.key.row / (MATRIX_ROWS / 2);

    return (tap_hand != other_hand);  // Opposite hands = instant hold
}

Example:

  • Press E (right hand, LCTL_T) + C (left hand) → Ctrl activates instantly (no timeout)
  • Press E (right hand) + I (right hand) → E taps (same hand roll)

Speculative Hold (Eager Mods)

Principle: Some modifiers apply immediately, can be canceled if determined as tap.

Configuration:

#define SPECULATIVE_HOLD
#define SPECULATIVE_HOLD_TIMEOUT 200

bool get_speculative_hold(uint16_t keycode, keyrecord_t* record) {
    uint8_t mod = QK_MOD_TAP_GET_MODS(keycode);

    switch (mod) {
        case MOD_LSFT:
        case MOD_RSFT:
        case MOD_LCTL:
        case MOD_RCTL:
            return true;   // Shift/Ctrl apply immediately

        case MOD_LALT:
        case MOD_RALT:
        case MOD_LGUI:
        case MOD_RGUI:
            return false;  // Alt/Win wait for decision
    }
}

Why Shift/Ctrl only:

  • Shift+Click, Ctrl+Click need instant response (mouse interaction)
  • Alt/Win can interfere with Alt+Tab, Win+Number if applied too early

Flow Tap (Typing Streaks)

Principle: During fast typing, force tap-holds to tap to prevent accidental mod activation.

Configuration:

#define FLOW_TAP
#define FLOW_TAP_TERM 200  // 200ms typing streak timeout

Example: Typing "того" quickly - the home row mods tap instead of activating.


Custom Keycodes

Language Control

  • LANG_ENG - Switch to English (F21 + U_BASE)
  • LANG_RUS - Toggle Russian (F22 + U_EXTRA)

Vim

  • VIM_SAVE - Esc :w Enter
  • VIM_CMD - Esc :

Punctuation (Always English)

  • MY_COMMA - , or <
  • MY_DOT - . or >
  • MY_QUESTION - ?!
  • MY_SLASH - / or ?
  • MY_SEMI - ;

Button Layer (Wayland)

  • BUT_A through BUT_Z - Ctrl+Alt+Win+(A-Z)

RGB

  • MY_RGB_MATRIX_TOGGLE - Toggle RGB (commented out in switch)

Layer Structure

BASE (Layer 0) - English Colemak-DH

Q  W  F  P  B        J  L  U  Y  '
A  R  S  T  G        M  N  E  I  O   (LGUI/LALT/LCTL/LSFT on home row)
Z  X  C  D  V        K  H  ,  .  /

   ESC SPC TAB      ENT BSPC DEL
   (MEDIA/NAV/MOUSE)(SYM/NUM/FUN)

Home Row Mods:

  • A: LGUI_T (Win)
  • R: LALT_T (Alt)
  • S: LCTL_T (Ctrl)
  • T: LSFT_T (Shift)
  • N: LSFT_T (Shift)
  • E: LCTL_T (Ctrl)
  • I: LALT_T (Alt)
  • O: LGUI_T (Win)

EXTRA (Layer 1) - Russian QWERTY

Q  Ю  Э  R  T        Y  Х  Ё  Б  P
A  S  D  F  G        H  J  K  L  ;
Z  X  C  V  B        N  Ъ  ,  .  /

   ESC SPC TAB      ENT BSPC DEL

Double-tap tap-dance keys:

  • E → э (EE)
  • W → ю (FF)
  • U → х (UU)
  • I → ё (II)
  • M → ъ (MM)
  • O → б (OO)

Custom punctuation: MY_COMMA, MY_DOT, MY_SLASH always send English punctuation

NUM (Layer 6) - Numbers

-  7  8  9  +        -       BASE    EXTRA   -       BOOT
:  4  5  6  =        -       SHIFT   CTRL    ALT     WIN
-  1  2  3  /        -       BSPC    TAB     CAPS    -

   .  0  -           -       -       -

Note: Numbers are in numpad arrangement for easy entry.

SYM (Layer 7) - Symbols

!  @  #  $  %        ^       &       *       (       )
|  ~  "  :  *        -       SHIFT   CTRL    ALT     WIN
\  `  '  ;  &        -       BSPC    TAB     CAPS    \

   (  )  _           -       -       -

FUN (Layer 8) - Function Keys

F12 F7  F8  F9 PSCR  -       BASE    EXTRA   -       BOOT
F11 F4  F5  F6 F14   -       SHIFT   CTRL    ALT     WIN
F10 F1  F2  F3 F13   -       -       -       -       -

    APP F16 F15      -       -       -

NAV (Layer 3) - Navigation

BOOT -   EXTRA BASE {        }       HOME    UP      END     PGUP
WIN  ALT CTRL  SHFT [        ]       LEFT    DOWN    RIGHT   PGDN
DBG  -   LEFT  RGHT ^        $       F23     C+F23   A+F23   W+F23

     -   -     -             -       DEL     INS

F23 keys: Special keys for custom shortcuts

MOUSE (Layer 4) - Mouse Control

BOOT -   EXTRA BASE -        ENG     WHLL    UP      WHLR    WHLU
WIN  ALT CTRL  SHFT -        RUS     LEFT    DOWN    RIGHT   WHLD
DBG  AC0 AC1   AC2  DEL      -       -       -       -       -

     -   -     -             BTN1    BTN2    BTN3

MEDIA (Layer 5) - Media Control

BOOT -   EXTRA BASE -        -       -       VOLU    -       RGB_TOG
WIN  ALT CTRL  SHFT -        -       PREV    VOLD    NEXT    -
-    ALGR FUN   MED  -        AUTO    -       -       -       -

     -   -     -             STOP    PLAY    MUTE

BUTTON (Layer 9) - Wayland Shortcuts

Q  W  F  P  B        J  L  U  Y  -
A  R  S  T  G        M  N  E  I  O
Z  X  C  D  V        K  H  -  -  -

   -  -  -           -  -  -

Each letter sends: Ctrl+Alt+Win+Letter


Combos

Language & Layers

  • T+N - Caps Word toggle
  • D+H - Switch to Russian
  • A+O - Win key (just meta)
  • Z+/ - Oneshot BUTTON layer

Text Entry

  • U+Y - Enter
  • L+U - Shift+Enter
  • C+D - Shift+Insert (paste)

Vim

  • W+F - VIM_SAVE (Esc :w Enter)
  • Z+Y - VIM_CMD (unused currently)

Punctuation

  • H+, - MY_COMMA
  • ,+. - MY_DOT
  • .+/ - MY_QUESTION

Debug

  • Q+' - MY_RGB_MATRIX_TOGGLE

Total: 14 combos

Configuration:

#define COMBO_COUNT 14
#define COMBO_TERM 200
#define COMBO_ONLY_FROM_LAYER 0  // Only BASE layer

Helper Functions

send_layout_signal()

Purpose: Send F21/F22 to Windows with no active modifiers.

Why: AHK needs clean F-key signals (no Shift/Ctrl/Alt held).

static inline void send_layout_signal(uint16_t kc) {
    uint8_t saved = get_mods();
    clear_mods();
    tap_code(kc);
    set_mods(saved);
}

send_in_english()

Purpose: Send arbitrary string in English, regardless of current layer.

Usage: Custom punctuation keys (MY_COMMA, MY_DOT, etc.)

void send_in_english(char* msg) {
    uint8_t mod_state = get_mods();
    bool russian_active = (U_EXTRA == get_highest_layer(layer_state));

    clear_mods();
    if (russian_active) switch_to_english();

    wait_ms(50);
    SEND_STRING(msg);
    wait_ms(50);

    if (russian_active) switch_to_russian();
    set_mods(mod_state);
}

switch_to_english()

Purpose: Permanent switch to English.

void switch_to_english(void) {
    send_layout_signal(KC_F21);  // Windows → English
    layer_move(U_BASE);          // QMK → BASE
}

switch_to_russian()

Purpose: Toggle to/from Russian.

void switch_to_russian(void) {
    if (U_EXTRA == get_highest_layer(layer_state)) {
        switch_to_english();  // Already Russian → go to English
    } else {
        send_layout_signal(KC_F22);  // Windows → Russian
        layer_move(U_EXTRA);         // QMK → EXTRA
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment