Complete documentation of all custom code and behaviors in this Miryoku setup.
- Overview
- Language Switching System
- Special Key Behaviors
- Home Row Mods Configuration
- Custom Keycodes
- Layer Structure
- Combos
- Helper Functions
- Configuration Settings
- Debug Logging
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
- 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
The system manages TWO independent states:
-
QMK Layers (firmware side):
U_BASE(0) - English Colemak-DH layoutU_EXTRA(1) - Russian QWERTY layout
-
Windows Keyboard Layout (OS side):
- Controlled by AutoHotKey via F21/F22 signals
- F21 → English
- F22 → Russian
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
}
}
}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)
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.
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)
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.
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).
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+"/")
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
VIM_SAVE: Sends Esc :w Enter (save file)
VIM_CMD: Sends Esc : (enter command mode)
Combos:
- W+F → VIM_SAVE
#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 configurationPrinciple: 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)
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
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 timeoutExample: Typing "того" quickly - the home row mods tap instead of activating.
- LANG_ENG - Switch to English (F21 + U_BASE)
- LANG_RUS - Toggle Russian (F22 + U_EXTRA)
- VIM_SAVE -
Esc :w Enter - VIM_CMD -
Esc :
- MY_COMMA -
,or< - MY_DOT -
.or> - MY_QUESTION -
?! - MY_SLASH -
/or? - MY_SEMI -
;
- BUT_A through BUT_Z - Ctrl+Alt+Win+(A-Z)
- MY_RGB_MATRIX_TOGGLE - Toggle RGB (commented out in switch)
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)
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
- 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.
! @ # $ % ^ & * ( )
| ~ " : * - SHIFT CTRL ALT WIN
\ ` ' ; & - BSPC TAB CAPS \
( ) _ - - -
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 - - -
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
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
BOOT - EXTRA BASE - - - VOLU - RGB_TOG
WIN ALT CTRL SHFT - - PREV VOLD NEXT -
- ALGR FUN MED - AUTO - - - -
- - - STOP PLAY MUTE
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
- T+N - Caps Word toggle
- D+H - Switch to Russian
- A+O - Win key (just meta)
- Z+/ - Oneshot BUTTON layer
- U+Y - Enter
- L+U - Shift+Enter
- C+D - Shift+Insert (paste)
- W+F - VIM_SAVE (Esc :w Enter)
- Z+Y - VIM_CMD (unused currently)
- H+, - MY_COMMA
- ,+. - MY_DOT
- .+/ - MY_QUESTION
- 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 layerPurpose: 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);
}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);
}Purpose: Permanent switch to English.
void switch_to_english(void) {
send_layout_signal(KC_F21); // Windows → English
layer_move(U_BASE); // QMK → BASE
}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
}
}