Created
February 3, 2025 21:16
-
-
Save bogorad/666544f12a168676b0fb22718a8d7c4d to your computer and use it in GitHub Desktop.
qmk + miryoku + achordion
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2022 Manna Harbour | |
// https://github.com/manna-harbour/miryoku | |
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. | |
#include "stdio.h" | |
#include QMK_KEYBOARD_H | |
#include "print.h" | |
#include "manna-harbour_miryoku.h" | |
#include "features/achordion.h" | |
#include "features/keycode_string.h" | |
#define SPLIT_LAYER_STATE_ENABLE | |
#define RGBLIGHT_EFFECT_ALTERNATING | |
//////////////////////////////////////////////// | |
// Define a structure to hold layer-tap information | |
typedef struct | |
{ | |
bool is_active; | |
bool command_sent; | |
uint16_t timer; | |
} layer_tap_t; | |
// Initialize structure instance for holding state | |
layer_tap_t my_lt = {false, false, 0}; | |
// system init | |
void keyboard_post_init_user(void) | |
{ | |
debug_enable=false; | |
debug_matrix=false; | |
debug_keyboard=false; | |
}; | |
bool need_to_restore_layer = false; | |
// Additional Features double tap guard | |
// tap-dance | |
enum { | |
U_TD_BOOT, | |
U_TD_RUS_YE, | |
U_TD_RUS_YU, | |
U_TD_RUS_HA, | |
U_TD_RUS_YO, | |
U_TD_RUS_TZ, | |
U_TD_RUS_B, | |
#define MIRYOKU_X(LAYER, STRING) U_TD_U_##LAYER, | |
MIRYOKU_LAYER_LIST | |
#undef MIRYOKU_X | |
}; | |
// reset keyboard into BOOT mode | |
void u_td_fn_boot(tap_dance_state_t *state, void *user_data) | |
{ | |
if (2 == state->count) | |
{ | |
reset_keyboard(); | |
} | |
} | |
// | |
#define MIRYOKU_X(LAYER, STRING) \ | |
void u_td_fn_U_##LAYER(tap_dance_state_t *state, void *user_data) { \ | |
if (2 == state->count) { \ | |
default_layer_set((layer_state_t)1 << U_##LAYER); \ | |
} \ | |
} | |
MIRYOKU_LAYER_LIST | |
#undef MIRYOKU_X | |
tap_dance_action_t tap_dance_actions[] = | |
{ | |
[U_TD_BOOT] = ACTION_TAP_DANCE_FN(u_td_fn_boot), | |
// Russian | |
[U_TD_RUS_YE] = ACTION_TAP_DANCE_DOUBLE(KC_T,KC_QUOT), | |
[U_TD_RUS_YU] = ACTION_TAP_DANCE_DOUBLE(KC_Y,KC_DOT), | |
[U_TD_RUS_HA] = ACTION_TAP_DANCE_DOUBLE(KC_U,KC_LBRC), | |
[U_TD_RUS_YO] = ACTION_TAP_DANCE_DOUBLE(KC_I,KC_GRAVE), | |
[U_TD_RUS_TZ] = ACTION_TAP_DANCE_DOUBLE(KC_M,KC_RBRC), | |
[U_TD_RUS_B] = ACTION_TAP_DANCE_DOUBLE(KC_H,KC_COMM), | |
// | |
#define MIRYOKU_X(LAYER, STRING) [U_TD_U_##LAYER] = ACTION_TAP_DANCE_FN(u_td_fn_U_##LAYER), | |
MIRYOKU_LAYER_LIST | |
#undef MIRYOKU_X | |
}; | |
enum custom_keycodes | |
{ | |
LANG_ENG = SAFE_RANGE, // Ensure these don't conflict with existing keycodes | |
LANG_RUS, | |
VIM_SAVE, | |
VIM_CMD, | |
LT_SYM_ENT, | |
LT_NUM_BSPC, | |
MY_COMMA, | |
MY_DOT, | |
MY_QUESTION, | |
MY_SLASH, | |
MY_RGB_MATRIX_TOGGLE, | |
BUT_A, BUT_B, BUT_C, | |
BUT_D, BUT_E, BUT_F, BUT_G, BUT_H, BUT_I, BUT_J, | |
BUT_K, BUT_L, BUT_M, BUT_N, BUT_O, BUT_P, BUT_Q, | |
BUT_R, BUT_S, BUT_T, BUT_U, BUT_V, BUT_W, BUT_X, | |
BUT_Y, BUT_Z, | |
}; | |
const keycode_string_name_t custom_keycode_names[] = { | |
// keycode, name. | |
{LANG_ENG, "LANG_ENG"}, | |
{LANG_RUS, "LANG_RUS"}, | |
{VIM_SAVE, "VIM_SAVE"}, | |
{VIM_CMD, "VIM_CMD"}, | |
{LT_SYM_ENT, "LT_SYM_ENT"}, | |
{LT_NUM_BSPC, "LT_NUM_BSPC"}, | |
{MY_COMMA, "MY_COMMA"}, | |
{MY_DOT, "MY_DOT"}, | |
{MY_QUESTION, "MY_QUESTION"}, | |
{MY_SLASH, "MY_SLASH"}, | |
{MY_RGB_MATRIX_TOGGLE, "MY_RGB_MATRIX_TOGGLE"}, | |
{BUT_A, "BUT_A"}, {BUT_B, "BUT_B"}, {BUT_C, "BUT_C"}, {BUT_D, "BUT_D"}, | |
{BUT_E, "BUT_E"}, {BUT_F, "BUT_F"}, {BUT_G, "BUT_G"}, {BUT_H, "BUT_H"}, | |
{BUT_I, "BUT_I"}, {BUT_J, "BUT_J"}, {BUT_K, "BUT_K"}, {BUT_L, "BUT_L"}, | |
{BUT_M, "BUT_M"}, {BUT_N, "BUT_N"}, {BUT_O, "BUT_O"}, {BUT_P, "BUT_P"}, | |
{BUT_Q, "BUT_Q"}, {BUT_R, "BUT_R"}, {BUT_S, "BUT_S"}, {BUT_T, "BUT_T"}, | |
{BUT_U, "BUT_U"}, {BUT_V, "BUT_V"}, {BUT_W, "BUT_W"}, {BUT_X, "BUT_X"}, | |
{BUT_Y, "BUT_Y"}, {BUT_Z, "BUT_Z"}, | |
{0, NULL}, // End of table sentinel. | |
}; | |
// keymap | |
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = | |
{ | |
#define MIRYOKU_X(LAYER, STRING) [U_##LAYER] = U_MACRO_VA_ARGS(MIRYOKU_LAYERMAPPING_##LAYER, MIRYOKU_LAYER_##LAYER), | |
MIRYOKU_LAYER_LIST | |
#undef MIRYOKU_X | |
}; | |
//////////////////////////////////////////////////////////////////// | |
// This switches to English but only when LT-key is pressed and another key it tapped. | |
// | |
// Function to handle layer-tap logic | |
void process_layer_tap(uint16_t keycode, uint16_t layer, keyrecord_t *record) | |
{ | |
if (record->event.pressed) | |
{ | |
// Key was pressed - start timer. | |
my_lt.timer = timer_read(); | |
my_lt.is_active = true; | |
layer_on(layer); | |
dprintf("lt: pressed!\n"); | |
} else { // Key was released | |
// Was it a tap? | |
if (my_lt.is_active && (TAPPING_TERM > timer_elapsed(my_lt.timer))) | |
{ | |
tap_code(keycode); | |
dprintf("lt: tapped!\n"); | |
} | |
layer_off(layer); | |
// Only restore win kbd layout if it was switched. | |
if (my_lt.command_sent) | |
{ | |
// Tell Windows we're still in Cyrillic | |
// SEND_STRING(SS_LSFT(SS_LALT("1"))); | |
tap_code(KC_F22); | |
// Clear flag. | |
my_lt.command_sent = false; | |
dprintf("lt: sent F22 to windows!\n"); | |
} | |
my_lt.is_active = false; | |
dprintf("lt: released!\n"); | |
} | |
} | |
//////////////////////////////////////////////////////////////////// | |
// shift functions | |
const key_override_t capsword_key_override = ko_make_basic(MOD_MASK_SHIFT, CW_TOGG, KC_CAPS); | |
const key_override_t *key_overrides[] = { | |
&capsword_key_override | |
}; | |
/////////////////////////////////////////////////////////////////////////// | |
// 4 | |
const uint16_t PROGMEM combo_caps_word[] = {LSFT_T(KC_T), LSFT_T(KC_N), COMBO_END}; // T+N | |
const uint16_t PROGMEM switch_to_rus[] = {KC_D, KC_H, COMBO_END}; | |
const uint16_t PROGMEM just_meta[] = {LGUI_T(KC_A), LGUI_T(KC_O), COMBO_END}; // A+O | |
const uint16_t PROGMEM buttons_oneshot[] = {KC_Z, KC_SLSH, COMBO_END}; | |
// enter/shift_enter/control_enter,tab/paste +5=9 | |
const uint16_t PROGMEM my_enter[] = {KC_U, KC_Y, COMBO_END}; | |
const uint16_t PROGMEM my_s_enter[] = {KC_L, KC_U, COMBO_END}; | |
const uint16_t PROGMEM my_c_enter[] = {LCTL_T(KC_E), LALT_T(KC_I), COMBO_END}; // E+I | |
const uint16_t PROGMEM my_rgb[] = {KC_Q, KC_QUOT, COMBO_END}; | |
const uint16_t PROGMEM my_paste[] = {KC_C, KC_D, COMBO_END}; | |
// vim +2=11 | |
const uint16_t PROGMEM vim_save[] = {KC_W, KC_F, COMBO_END}; | |
const uint16_t PROGMEM vim_cmd[] = {KC_Z, KC_Y, COMBO_END}; // unused | |
// ,.? +3=14 | |
const uint16_t PROGMEM my_comma[] = {KC_H, KC_COMM, COMBO_END}; | |
const uint16_t PROGMEM my_dot[] = {KC_COMM, KC_DOT, COMBO_END}; | |
const uint16_t PROGMEM my_question[] = {KC_DOT, KC_SLSH, COMBO_END}; | |
/////////////////////////////////////////////////////////////////////////// | |
combo_t key_combos[COMBO_COUNT] = | |
{ | |
COMBO(combo_caps_word, QK_CAPS_WORD_TOGGLE), | |
COMBO(switch_to_rus, LANG_RUS), | |
COMBO(just_meta, KC_LGUI), | |
COMBO(buttons_oneshot, OSL(U_BUTTON)), | |
// | |
COMBO(my_enter, KC_ENTER), | |
COMBO(my_s_enter, S(KC_ENTER)), | |
COMBO(my_c_enter, C(KC_ENTER)), | |
COMBO(my_rgb, MY_RGB_MATRIX_TOGGLE), | |
COMBO(my_paste, S(KC_INS)), | |
// | |
COMBO(vim_save, VIM_SAVE), | |
COMBO(vim_cmd, VIM_CMD), // redefine | |
// | |
COMBO(my_comma, MY_COMMA), | |
COMBO(my_dot, MY_DOT), | |
COMBO(my_question, MY_QUESTION), | |
}; | |
//////////////// here we switch languages | |
// | |
// define fallback first | |
void switch_to_english(void) | |
{ | |
// debug | |
dprint("Switched to English!\n"); | |
tap_code(KC_F21); | |
// Switch to default layer. | |
layer_move(U_BASE); | |
}; | |
#define RUS_LAYER_TIMEOUT 3000 // timeout in milliseconds | |
void switch_to_russian(void) | |
{ | |
// switch back to English if already in Russian mode | |
if (U_EXTRA == get_highest_layer(layer_state)) | |
{ | |
dprint("Russian active! Switching back to English\n"); | |
switch_to_english(); | |
} | |
else | |
{ | |
// debug | |
dprint("Switched to Russian!\n"); | |
// | |
tap_code(KC_F22); | |
// Switch to Cyrillic-friendly layer. | |
layer_move(U_EXTRA); | |
}; | |
}; | |
///////////////////////////////////////////////// | |
void matrix_scan_user(void) | |
{ | |
// achordion | |
achordion_task(); | |
// | |
// this resets current language to English after <timeout> if Russian was active | |
if (U_EXTRA == get_highest_layer(layer_state)) | |
{ | |
if (RUS_LAYER_TIMEOUT < last_input_activity_elapsed()) | |
{ | |
switch_to_english(); | |
} | |
} | |
} | |
///////////////////////////////////////////////// | |
bool achordion_chord(uint16_t tap_hold_keycode, | |
keyrecord_t* tap_hold_record, | |
uint16_t other_keycode, | |
keyrecord_t* other_record) | |
{ | |
// | |
// When I hold a thumb key, allow everything. | |
// I need the `% (MATRIX_ROWS / 2)` because my keyboard is split. | |
// My rows are: 012/3=left-thumbs, 456/7=right-thumbs. | |
if (3 == tap_hold_record->event.key.row % (MATRIX_ROWS / 2)) { return true; } | |
// | |
// Otherwise, follow the opposite hands rule. | |
return achordion_opposite_hands(tap_hold_record, other_record); | |
} | |
///////////////////////////////////////////////// | |
///////////////////////////////////////////////// | |
void send_in_english(char* msg) | |
{ | |
// Save mods. | |
uint8_t mod_state = get_mods(); | |
// check if Russian is active | |
bool russian_active = (U_EXTRA == get_highest_layer(layer_state)); | |
dprintf("send_in_english: Russian active: %d\n", russian_active); | |
// Clear mods so Windows doesn't get confused. | |
clear_mods(); | |
if (russian_active) | |
switch_to_english(); | |
// send whatever | |
dprintf("send_in_english: sent \"%s\"\n", msg); | |
wait_ms(50); // give AHK time to switch languages | |
SEND_STRING(msg); | |
wait_ms(50); // 50 milliseconds delay | |
// restore russian if it was active | |
if (russian_active) | |
switch_to_russian(); | |
// Restore mods. | |
set_mods(mod_state); | |
} | |
///////////////////////////////////////////////// | |
static void dlog_record(uint16_t keycode, keyrecord_t* record) { | |
if (!debug_enable) { return; } | |
uint8_t layer = read_source_layers_cache(record->event.key); | |
bool is_tap_hold = IS_QK_MOD_TAP(keycode) || IS_QK_LAYER_TAP(keycode); | |
dprintf("L%-2u ", layer); // Log the layer. | |
if (IS_COMBOEVENT(record->event)) { // Combos don't have a position. | |
dprintf("combo "); | |
} else { // Log the "(row,col)" position. | |
dprintf("(%2u,%2u) ", record->event.key.row, record->event.key.col); | |
} | |
dprintf("%-4s %-7s %s\n", // "(tap|hold) (press|release) <keycode>". | |
is_tap_hold ? (record->tap.count ? "tap" : "hold") : "", | |
record->event.pressed ? "press" : "release", | |
keycode_string(keycode)); | |
} | |
///////////////////////////////////////////////// | |
// macro processor etc | |
bool process_record_user(uint16_t keycode, keyrecord_t *record) | |
{ | |
uint8_t mod_state = get_mods(); | |
// | |
// only print to HID console when debugging is on | |
dlog_record(keycode, record); | |
// achordion | |
if (!process_achordion(keycode, record)) { return false; } | |
// Check if the current layer is U_EXTRA | |
// and key is pressed | |
// and HRMs are active | |
if (layer_state_cmp(layer_state, U_EXTRA) && | |
record->event.pressed && | |
(mod_state & (MOD_BIT(KC_LALT) | | |
MOD_BIT(KC_LCTL) | | |
MOD_BIT(KC_LGUI)))) | |
{ | |
if (keycode == KC_LEFT || keycode == KC_RIGHT || | |
keycode == KC_UP || keycode == KC_DOWN || | |
keycode == KC_PGUP || keycode == KC_PGDN || | |
keycode == KC_HOME || keycode == KC_END) | |
{ // no need to switch languages for navigation keys | |
dprintf("HRMs active, but navigation, not switching to U_BASE\n"); | |
// return true; // Continue normal tab processing | |
} | |
else | |
{ // let the fuckery begin... | |
dprintf("HRMs active, switching to U_BASE\n"); | |
layer_move(U_BASE); | |
need_to_restore_layer = true; | |
// return true; // Continue normal tab processing | |
} | |
} | |
if (!record->event.pressed && | |
need_to_restore_layer) | |
{ | |
dprintf("Restoring U_EXTRA\n"); | |
layer_move(U_EXTRA); | |
need_to_restore_layer = false; | |
return true; // Continue normal tab processing | |
} | |
// fist check for buttons | |
// we need this because Wayland fucks up and a race condition occurs | |
if (keycode >= BUT_A && keycode <= BUT_Z) { | |
int new_keycode = KC_A + keycode - BUT_A; // remap keycode | |
if (record->event.pressed) { | |
// On key press | |
clear_mods(); // Temporarily 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); | |
return false; | |
} else { | |
// On key release | |
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); | |
return false; | |
} | |
} | |
// check for specific keycodes | |
switch (keycode) | |
{ | |
// user keys first | |
case LANG_ENG: | |
if (record->event.pressed) | |
switch_to_english(); | |
return false; // Skip further processing | |
case LANG_RUS: | |
if (record->event.pressed) | |
switch_to_russian(); | |
return false; // Skip further processing | |
case VIM_SAVE: | |
if (record->event.pressed) | |
{ | |
SEND_STRING(SS_TAP(X_ESCAPE)":w"SS_TAP(X_ENTER)); | |
dprint("vim->saved!\n"); | |
} | |
return false; // Skip further processing | |
case VIM_CMD: | |
if (record->event.pressed) | |
{ | |
SEND_STRING(SS_TAP(X_ESCAPE)":"); | |
dprint("vim->command.\n"); | |
} | |
return false; // Skip further processing | |
// special consideration for TAB & ESCAPE & digits | |
case LT(U_MOUSE,KC_TAB): | |
case LT(U_MEDIA,KC_ESC): | |
// only operate if pressed | |
if (record->event.pressed && | |
// Ignore if held | |
record->tap.count && | |
// Check if the current layer is U_EXTRA | |
layer_state_cmp(layer_state, U_EXTRA) && | |
// And any of the modifiers are active | |
(mod_state & (MOD_BIT(KC_LALT) | | |
MOD_BIT(KC_LSFT) | | |
MOD_BIT(KC_LCTL) | | |
MOD_BIT(KC_LGUI) | |
))) | |
{ | |
switch_to_english(); | |
}; | |
return true; // Continue normal tab processing | |
case MY_COMMA: | |
if (record->event.pressed) | |
{ | |
dprintf("MY_COMMA pressed!\n"); | |
if (mod_state & MOD_BIT(KC_LSFT)) | |
send_in_english(SS_LSFT(",")); | |
else | |
send_in_english(","); | |
} | |
return false; // Skip all further processing of this key | |
case MY_DOT: | |
if (record->event.pressed) | |
{ | |
dprintf("MY_COMMA pressed!\n"); | |
if (mod_state & MOD_BIT(KC_LSFT)) | |
send_in_english(SS_LSFT(".")); | |
else | |
send_in_english("."); | |
} | |
return false; // Skip all further processing of this key | |
case MY_QUESTION: | |
if (record->event.pressed) | |
{ | |
dprintf("MY_QUESTION pressed!\n"); | |
send_in_english(SS_LSFT("/")); | |
} | |
return false; // Skip all further processing of this key | |
case MY_SLASH: | |
if (record->event.pressed) | |
{ | |
dprintf("MY_COMMA pressed!\n"); | |
if (mod_state & MOD_BIT(KC_LSFT)) | |
send_in_english(SS_LSFT("/")); | |
else | |
send_in_english("/"); | |
} | |
return false; // Skip all further processing of this key | |
case MY_RGB_MATRIX_TOGGLE: | |
rgb_matrix_toggle(); | |
return false; // Skip all further processing of this key | |
case LT_SYM_ENT: | |
process_layer_tap(KC_ENT, U_SYM, record); | |
return false; // Skip all further processing of this key | |
case LT_NUM_BSPC: | |
process_layer_tap(KC_BSPC, U_NUM, record); | |
return false; // Skip all further processing of this key | |
default: | |
if (my_lt.is_active && record->event.pressed) | |
{ | |
// Only switch win kbd layout if it has not been switched. | |
if (!my_lt.command_sent) | |
{ | |
// Custom function called when any key is pressed while MY_LT is active | |
my_lt.command_sent = true; | |
// SEND_STRING(SS_LSFT(SS_LALT("0"))); | |
tap_code(KC_F21); | |
dprintf("lt: sent F21 to windows!\n"); | |
} | |
return true; // Allow the key press to be handled normally by QMK | |
} | |
break; | |
} // done with specific keys | |
return true; | |
} | |
////////////////////////////// | |
bool rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) { | |
if (get_highest_layer(layer_state) == U_EXTRA) | |
rgb_matrix_enable(); | |
else | |
rgb_matrix_disable(); | |
return false; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment