├── private_keycodes.h ├── include.h ├── README.md └── src.c /private_keycodes.h: -------------------------------------------------------------------------------- 1 | KS_GRV, /* ` */ 2 | KS_TILD, /* ~ */ 3 | KS_1, /* 1 */ 4 | KS_EXCL, /* ! */ 5 | KS_2, /* 2 */ 6 | KS_AT, /* @ */ 7 | KS_3, /* 3 */ 8 | KS_HASH, /* # */ 9 | KS_4, /* 4 */ 10 | KS_DLR, /* $ */ 11 | KS_5, /* 5 */ 12 | KS_PERC, /* % */ 13 | KS_6, /* 6 */ 14 | KS_CIRC, /* ^ */ 15 | KS_7, /* 7 */ 16 | KS_AMPR, /* & */ 17 | KS_8, /* 8 */ 18 | KS_ASTR, /* * */ 19 | KS_9, /* 9 */ 20 | KS_LPRN, /* ( */ 21 | KS_0, /* 0 */ 22 | KS_RPRN, /* ) */ 23 | KS_MINS, /* - */ 24 | KS_UNDS, /* _ */ 25 | KS_EQL, /* = */ 26 | KS_PLUS, /* + */ 27 | 28 | KS_Q, /* q */ 29 | KS_S_Q, /* Q */ 30 | KS_W, /* w */ 31 | KS_S_W, /* W */ 32 | KS_E, /* e */ 33 | KS_S_E, /* E */ 34 | KS_R, /* r */ 35 | KS_S_R, /* R */ 36 | KS_T, /* t */ 37 | KS_S_T, /* T */ 38 | KS_Y, /* y */ 39 | KS_S_Y, /* Y */ 40 | KS_U, /* u */ 41 | KS_S_U, /* U */ 42 | KS_I, /* i */ 43 | KS_S_I, /* I */ 44 | KS_O, /* o */ 45 | KS_S_O, /* O */ 46 | KS_P, /* p */ 47 | KS_S_P, /* P */ 48 | KS_LBRC, /* [ */ 49 | KS_LCBR, /* { */ 50 | KS_RBRC, /* ] */ 51 | KS_RCBR, /* } */ 52 | 53 | KS_A, /* a */ 54 | KS_S_A, /* A */ 55 | KS_S, /* s */ 56 | KS_S_S, /* S */ 57 | KS_D, /* d */ 58 | KS_S_D, /* D */ 59 | KS_F, /* f */ 60 | KS_S_F, /* F */ 61 | KS_G, /* g */ 62 | KS_S_G, /* G */ 63 | KS_H, /* h */ 64 | KS_S_H, /* H */ 65 | KS_J, /* j */ 66 | KS_S_J, /* J */ 67 | KS_K, /* k */ 68 | KS_S_K, /* K */ 69 | KS_L, /* l */ 70 | KS_S_L, /* L */ 71 | KS_SCLN, /* ; */ 72 | KS_COLN, /* : */ 73 | KS_QUOT, /* ' */ 74 | KS_DQUO, /* " */ 75 | KS_BSLS, /* \ */ 76 | KS_PIPE, /* | */ 77 | 78 | #ifndef LANG_USE_ANSI 79 | KS_LT2, /* < */ 80 | KS_GT2, /* > */ 81 | #endif 82 | KS_Z, /* z */ 83 | KS_S_Z, /* Z */ 84 | KS_X, /* x */ 85 | KS_S_X, /* X */ 86 | KS_C, /* c */ 87 | KS_S_C, /* C */ 88 | KS_V, /* v */ 89 | KS_S_V, /* V */ 90 | KS_B, /* b */ 91 | KS_S_B, /* B */ 92 | KS_N, /* n */ 93 | KS_S_N, /* N */ 94 | KS_M, /* m */ 95 | KS_S_M, /* M */ 96 | KS_COMM, /* , */ 97 | KS_LT, /* < */ 98 | KS_DOT, /* . */ 99 | KS_GT, /* > */ 100 | KS_SLSH, /* / */ 101 | KS_QUES, /* ? */ 102 | -------------------------------------------------------------------------------- /include.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef CUSTOM_SAFE_RANGE 4 | #error "You must specify variable CUSTOM_SAFE_RANGE for lang_shift extension." 5 | #endif 6 | 7 | enum lang_shift_keycodes { 8 | LANG_SHIFT_START = CUSTOM_SAFE_RANGE, 9 | 10 | // Кейкоды, на основе которых реализуются кейкоды ниже, пользователю их лучше не использовать. 11 | #include "private_keycodes.h" 12 | 13 | /* -------------------------------------------------------------------- */ 14 | /* Английские символы */ 15 | EN_GRV, /* ` */ 16 | EN_TILD, /* ~ */ 17 | EN_1, /* 1 */ 18 | EN_EXCL, /* ! */ 19 | EN_2, /* 2 */ 20 | EN_AT, /* @ */ 21 | EN_3, /* 3 */ 22 | EN_HASH, /* # */ 23 | EN_4, /* 4 */ 24 | EN_DLR, /* $ */ 25 | EN_5, /* 5 */ 26 | EN_PERC, /* % */ 27 | EN_6, /* 6 */ 28 | EN_CIRC, /* ^ */ 29 | EN_7, /* 7 */ 30 | EN_AMPR, /* & */ 31 | EN_8, /* 8 */ 32 | EN_ASTR, /* * */ 33 | EN_9, /* 9 */ 34 | EN_LPRN, /* ( */ 35 | EN_0, /* 0 */ 36 | EN_RPRN, /* ) */ 37 | EN_MINS, /* - */ 38 | EN_UNDS, /* _ */ 39 | EN_EQL, /* = */ 40 | EN_PLUS, /* + */ 41 | 42 | EN_Q, /* q */ 43 | EN_S_Q, /* Q */ 44 | EN_W, /* w */ 45 | EN_S_W, /* W */ 46 | EN_E, /* e */ 47 | EN_S_E, /* E */ 48 | EN_R, /* r */ 49 | EN_S_R, /* R */ 50 | EN_T, /* t */ 51 | EN_S_T, /* T */ 52 | EN_Y, /* y */ 53 | EN_S_Y, /* Y */ 54 | EN_U, /* u */ 55 | EN_S_U, /* U */ 56 | EN_I, /* i */ 57 | EN_S_I, /* I */ 58 | EN_O, /* o */ 59 | EN_S_O, /* O */ 60 | EN_P, /* p */ 61 | EN_S_P, /* P */ 62 | EN_LBRC, /* [ */ 63 | EN_LCBR, /* { */ 64 | EN_RBRC, /* ] */ 65 | EN_RCBR, /* } */ 66 | 67 | EN_A, /* a */ 68 | EN_S_A, /* A */ 69 | EN_S, /* s */ 70 | EN_S_S, /* S */ 71 | EN_D, /* d */ 72 | EN_S_D, /* D */ 73 | EN_F, /* f */ 74 | EN_S_F, /* F */ 75 | EN_G, /* g */ 76 | EN_S_G, /* G */ 77 | EN_H, /* h */ 78 | EN_S_H, /* H */ 79 | EN_J, /* j */ 80 | EN_S_J, /* J */ 81 | EN_K, /* k */ 82 | EN_S_K, /* K */ 83 | EN_L, /* l */ 84 | EN_S_L, /* L */ 85 | EN_SCLN, /* ; */ 86 | EN_COLN, /* : */ 87 | EN_QUOT, /* ' */ 88 | EN_DQUO, /* " */ 89 | EN_BSLS, /* \ */ 90 | EN_PIPE, /* | */ 91 | 92 | #ifndef LANG_USE_ANSI 93 | EN_LT2, /* < */ 94 | EN_GT2, /* > */ 95 | #endif 96 | EN_Z, /* z */ 97 | EN_S_Z, /* Z */ 98 | EN_X, /* x */ 99 | EN_S_X, /* X */ 100 | EN_C, /* c */ 101 | EN_S_C, /* C */ 102 | EN_V, /* v */ 103 | EN_S_V, /* V */ 104 | EN_B, /* b */ 105 | EN_S_B, /* B */ 106 | EN_N, /* n */ 107 | EN_S_N, /* N */ 108 | EN_M, /* m */ 109 | EN_S_M, /* M */ 110 | EN_COMM, /* , */ 111 | EN_LT, /* < */ 112 | EN_DOT, /* . */ 113 | EN_GT, /* > */ 114 | EN_SLSH, /* / */ 115 | EN_QUES, /* ? */ 116 | 117 | /* -------------------------------------------------------------------- */ 118 | /* Русские символы */ 119 | RU_JO, /* ё */ 120 | RU_S_JO, /* Ё */ 121 | RU_1, /* 1 */ 122 | RU_EXCL, /* ! */ 123 | RU_2, /* 2 */ 124 | RU_DQUO, /* " */ 125 | RU_3, /* 3 */ 126 | RU_NUME, /* № */ 127 | RU_4, /* 4 */ 128 | RU_SCLN, /* ; */ 129 | RU_5, /* 5 */ 130 | RU_PERC, /* % */ 131 | RU_6, /* 6 */ 132 | RU_COLN, /* : */ 133 | RU_7, /* 7 */ 134 | RU_QUES, /* ? */ 135 | RU_8, /* 8 */ 136 | RU_ASTR, /* * */ 137 | RU_9, /* 9 */ 138 | RU_LPRN, /* ( */ 139 | RU_0, /* 0 */ 140 | RU_RPRN, /* ) */ 141 | RU_MINS, /* - */ 142 | RU_UNDS, /* _ */ 143 | RU_EQL, /* = */ 144 | RU_PLUS, /* + */ 145 | 146 | RU_J, /* й */ 147 | RU_S_J, /* Й */ 148 | RU_TS, /* ц */ 149 | RU_S_TS, /* Ц */ 150 | RU_U, /* у */ 151 | RU_S_U, /* У */ 152 | RU_K, /* к */ 153 | RU_S_K, /* К */ 154 | RU_JE, /* е */ 155 | RU_S_JE, /* Е */ 156 | RU_N, /* н */ 157 | RU_S_N, /* Н */ 158 | RU_G, /* г */ 159 | RU_S_G, /* Г */ 160 | RU_SH, /* ш */ 161 | RU_S_SH, /* Ш */ 162 | RU_SC, /* щ */ 163 | RU_S_SC, /* Щ */ 164 | RU_Z, /* з */ 165 | RU_S_Z, /* З */ 166 | RU_H, /* х */ 167 | RU_S_H, /* Х */ 168 | RU_HD, /* ъ */ 169 | RU_S_HD, /* Ъ */ 170 | 171 | RU_F, /* ф */ 172 | RU_S_F, /* Ф */ 173 | RU_Y, /* ы */ 174 | RU_S_Y, /* Ы */ 175 | RU_V, /* в */ 176 | RU_S_V, /* В */ 177 | RU_A, /* а */ 178 | RU_S_A, /* А */ 179 | RU_P, /* п */ 180 | RU_S_P, /* п */ 181 | RU_R, /* р */ 182 | RU_S_R, /* Р */ 183 | RU_O, /* о */ 184 | RU_S_O, /* О */ 185 | RU_L, /* л */ 186 | RU_S_L, /* Л */ 187 | RU_D, /* д */ 188 | RU_S_D, /* Д */ 189 | RU_ZH, /* ж */ 190 | RU_S_ZH, /* Ж */ 191 | RU_E, /* э */ 192 | RU_S_E, /* Э */ 193 | RU_BSLS, /* \ */ 194 | RU_SLSH, /* / */ 195 | 196 | #ifndef LANG_USE_ANSI 197 | RU_SLS2, /* / */ 198 | RU_PIPE, /* | */ 199 | #endif 200 | RU_JA, /* я */ 201 | RU_S_JA, /* Я */ 202 | RU_CH, /* ч */ 203 | RU_S_CH, /* Ч */ 204 | RU_S, /* с */ 205 | RU_S_S, /* С */ 206 | RU_M, /* м */ 207 | RU_S_M, /* М */ 208 | RU_I, /* и */ 209 | RU_S_I, /* И */ 210 | RU_T, /* т */ 211 | RU_S_T, /* Т */ 212 | RU_SF, /* ь */ 213 | RU_S_SF, /* Ь */ 214 | RU_B, /* б */ 215 | RU_S_B, /* Б */ 216 | RU_JU, /* ю */ 217 | RU_S_JU, /* Ю */ 218 | RU_DOT, /* . */ 219 | RU_COMM, /* , */ 220 | 221 | /* -------------------------------------------------------------------- */ 222 | /* Символы, нажимаемые независимо от текущего языка. Language-agnostic-keycodes. */ 223 | AG_1, /* 1 */ 224 | AG_2, /* 2 */ 225 | AG_3, /* 3 */ 226 | AG_4, /* 4 */ 227 | AG_5, /* 5 */ 228 | AG_6, /* 6 */ 229 | AG_7, /* 7 */ 230 | AG_8, /* 8 */ 231 | AG_9, /* 9 */ 232 | AG_0, /* 0 */ 233 | AG_EXCL, /* ! */ 234 | AG_PERC, /* % */ 235 | AG_ASTR, /* * */ 236 | AG_LPRN, /* ( */ 237 | AG_RPRN, /* ) */ 238 | AG_MINS, /* - */ 239 | AG_UNDS, /* _ */ 240 | AG_EQL, /* = */ 241 | AG_PLUS, /* + */ 242 | AG_SCLN, /* ; */ 243 | AG_COLN, /* : */ 244 | AG_DQUO, /* " */ 245 | AG_BSLS, /* \ */ 246 | #ifndef LANG_USE_ANSI 247 | AG_PIPE, /* | */ 248 | #endif 249 | AG_COMM, /* , */ 250 | AG_DOT, /* . */ 251 | AG_SLSH, /* / */ 252 | #ifndef LANG_USE_ANSI 253 | AG_S_SL, /* / */ // This keycode uses the slash with shift on Russian layout instead the slash without shift 254 | #endif 255 | AG_QUES, /* ? */ 256 | 257 | /* -------------------------------------------------------------------- */ 258 | /* Кастомные language-agnostic-кейкоды */ 259 | AG_3DOT, /* ... */ 260 | AG_SDOT, /* Space + Dot + Shift to next key */ 261 | AG_CMSP, /* Comma + Space */ 262 | 263 | /* -------------------------------------------------------------------- */ 264 | /* Шифты для набора текста. */ 265 | SFT_N, /* Работает как обычный шифт, при её зажатии, шифт зажат. Параллельно переключает на (шифтовый) следующий слой относительно текущего языка (если язык 0, то включает слой 1, если язык 1, то включает слой 3). */ 266 | SFT_N_O, /* Данная клавиша применит шифт и переключение на шифтовый слой только к следующему символу. Неважно, была ли она нажата одновременно со следующей клавишей, или до неё, или вместе с двумя клавишами. Применится только к одной клавише. То есть это как залипающий шифт, если нажимать отдельно от всех клавиш, и как одиночный шифт, если эту клавишу зажимать. Такая клавиша может пригодиться если вы печатаете с большой скоростью и вам сложно выдерживать время удержания шифта, и у вас получается что-то вроде "ПРивет". */ 267 | 268 | /* -------------------------------------------------------------------- */ 269 | /* Модификаторы, переключащие текущий слой на 0. Такое нужно чтобы при включённом русском языке были такие же хоткеи с модификаторами, как и на английском языке. */ 270 | CTRL_0, /* Ctrl on 0 layer */ 271 | ALT_0, /* Alt on 0 layer */ 272 | WIN_0, /* Win on 0 layer */ 273 | SHAL_0, /* Shift+Alt on 0 layer */ 274 | CTAL_0, /* Ctrl+Alt on 0 layer */ 275 | CTSH_0, /* Ctrl+Shift on 0 layer */ 276 | MCAS_0, /* Ctrl+Alt+Shift on 0 layer */ 277 | 278 | /* А данные модификаторы помимо включения 0 слоя ещё и переключают язык на английский. Такое разделение нужно, потому что у некоторых смена языка идёт на комбинации с Ctrl, или Shift, и у них может сниматься выделение текста, например. */ 279 | CTRL_EN, /* Ctrl on 0 layer */ 280 | ALT_EN, /* Alt on 0 layer */ 281 | WIN_EN, /* Win on 0 layer */ 282 | SHAL_EN, /* Shift+Alt on 0 layer */ 283 | CTAL_EN, /* Ctrl+Alt on 0 layer */ 284 | CTSH_EN, /* Ctrl+Shift on 0 layer */ 285 | MCAS_EN, /* Ctrl+Alt+Shift on 0 layer */ 286 | 287 | /* -------------------------------------------------------------------- */ 288 | /* Кнопки, отвечающие за переключение языка. */ 289 | LA_CHNG, /* Переключает язык и переключает на 2 или 0 слой в зависимости от текущего языка. */ 290 | LA_SYNC, /* Кнопка для синхронизации языков. Её нужно нажимать в случае если язык в системе и язык в клавиатуре различаются. Данная клавиша изменит язык в системе. */ 291 | LA_CAPS, /* Задаёт переключение языка на Caps. */ 292 | LA_ALSH, /* Задаёт переключение языка на Alt + Shift. */ 293 | LA_SHAL, /* Задаёт переключение языка на Shift + Alt. На Win10 это позволяет избежать появления окна переключения языков. */ 294 | LA_CTSH, /* Задаёт переключение языка на Ctrl + Shift. */ 295 | LA_SHCT, /* Задаёт переключение языка на Shift + Ctrl. На Win10 это позволяет избежать появления окна переключения языков. */ 296 | LA_WISP, /* Задаёт переключение языка на Win + Shift. */ 297 | 298 | /* -------------------------------------------------------------------- */ 299 | /* SAFE_RANGE данной библиотеки. */ 300 | LANG_SHIFT_NEW_SAFE_RANGE, 301 | #undef CUSTOM_SAFE_RANGE 302 | #define CUSTOM_SAFE_RANGE LANG_SHIFT_NEW_SAFE_RANGE 303 | }; 304 | 305 | typedef uint16_t Key; 306 | #define NONE_KEY (uint16_t)(65535) 307 | 308 | typedef uint8_t Lang; 309 | #define NONE_LANG (uint8_t)(255) 310 | 311 | typedef uint8_t Shift; 312 | #define NONE_SHIFT (uint8_t)(255) 313 | 314 | // Способ смены языка 315 | enum LangChange { 316 | LANG_CHANGE_CAPS, 317 | LANG_CHANGE_ALT_SHIFT, 318 | LANG_CHANGE_SHIFT_ALT, 319 | LANG_CHANGE_CTRL_SHIFT, 320 | LANG_CHANGE_SHIFT_CTRL, 321 | LANG_CHANGE_WIN_SPACE 322 | }; 323 | 324 | // Переменная, в которой можно менять текущий способ смены языка 325 | enum LangChange lang_current_change = 326 | #ifdef LANG_CHANGE_DEFAULT 327 | LANG_CHANGE_DEFAULT; 328 | #else 329 | #error "You must specify default language change method by defining variable LANG_CHANGE_DEFAULT." 330 | #endif 331 | 332 | void shift_activate(Shift shift); 333 | void shift_activate_from_user(Shift shift); 334 | 335 | void shift_once_use_to_next_key(uint8_t layer); 336 | 337 | uint8_t lang_get_shift_layer_number(void); 338 | void lang_synchronize(void); 339 | void lang_activate(Lang lang); 340 | void lang_activate_from_user(Lang lang); 341 | 342 | void lang_shift_press_key(Key key, bool down); 343 | void lang_shift_tap_key(Key key); 344 | 345 | bool lang_shift_process_record(Key key, keyrecord_t* record); 346 | void lang_shift_user_timer(void); 347 | 348 | // Инклюжу код напрямую, потому что нельзя сделать линковку, ведь код внутри использует кейкоды отсюда, и обязательно нужно это делать через safe_range 349 | #include "src.c" 350 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Кастомные русская и английская раскладки 2 | 3 | Данный модуль позволяет вам: 4 | * Задавать кастомную английскую и русскую раскладку прямо в прошивке клавиатуры. 5 | * То есть вы сможете прямо внутри клавиатуры запрограммировать раскладку Диктор и Дворак для программистов. 6 | * Быть полностью независимым от софта. То есть данная раскладка будет работать на любой системе, на абсолютно стандартных раскладках QWERTY и ЙЦУКЕН, и вам не нужно создавать и устанавливать свою раскладку в MKLC, например. 7 | * Утверждение верно только для Windows, GNU/Linux, Android. 8 | * Для MacOS для русского надо поставить раскладку "Русский PC". Ибо по умолчанию там какая-то своя машинописная раскладка, а эта в точности та же что и на Windows/Linux. 9 | * Нажимать символы с шифтом, когда включён основной слой (можно симулировать раскладку Русская Машинопись). 10 | * Нажимать символы без шифта, когда нажат шифт (можно симулировать ту часть Русской Машинописи, когда при зажатом шифте можно набирать цифры). 11 | * Нажимать английские символы при включённой русской раскладке и наоборот. Это позволит вам создать раскладко-независимый способ набирать символы, и больше вам не придётся переключать язык чтобы набрать квадратные скобки, `` ` `` из русского или `№` из английского. 12 | * При нажатии модификаторов Ctrl, Alt, Win etc. раскладка клавиатуры будет считаться английской, и у вас не будет проблемы в том что на русском языке хоткеи расположены в одном месте, а на английском языке по-другому. 13 | * Использовать особый шифт, который применяется только к следующей клавише, и использовать его в составе собственных макросов, например можно создать макрос, который набирает вопросительный знак, затем пробел, и затем автоматически применяет шифт к следующей нажатой клавише. 14 | 15 | Обратите внимание, что данный модуль работает через расширение пользовательских клавиш, то есть многие фичи QMK что работают с `KC_A` не будут работать с `EN_A`, например [Combos](https://docs.qmk.fm/#/feature_combo). 16 | 17 | # Как использовать? 18 | 19 | ## Задать характеристики 20 | 21 | Нужно задать способ переключения языка по умолчанию в переменной препроцессора `LANG_CHANGE_DEFAULT` в файле `config.h`, используя один из вариантов: 22 | 23 | ``` 24 | LANG_CHANGE_CAPS 25 | LANG_CHANGE_ALT_SHIFT 26 | LANG_CHANGE_SHIFT_ALT 27 | LANG_CHANGE_CTRL_SHIFT 28 | LANG_CHANGE_SHIFT_CTRL 29 | LANG_CHANGE_WIN_SPACE 30 | ``` 31 | 32 | То есть в файле `config.h` должна быть строка следующего вида: 33 | ``` 34 | #define LANG_CHANGE_DEFAULT LANG_CHANGE_ALT_SHIFT 35 | ``` 36 | 37 | Так же этими вариантами можно задавать способ переключения языка во время работы раскладки, модифицируя переменную `lang_current_change`, либо прямо на раскладке используя кейкоды: 38 | 39 | ```c 40 | LA_CAPS, /* Задаёт переключение языка на Caps. */ 41 | LA_ALSH, /* Задаёт переключение языка на Alt + Shift. */ 42 | LA_SHAL, /* Задаёт переключение языка на Shift + Alt. На Win10 это позволяет избежать появления окна переключения языков. */ 43 | LA_CTSH, /* Задаёт переключение языка на Ctrl + Shift. */ 44 | LA_SHCT, /* Задаёт переключение языка на Shift + Ctrl. На Win10 это позволяет избежать появления окна переключения языков. */ 45 | LA_WISP, /* Задаёт переключение языка на Win + Space. */ 46 | ``` 47 | 48 | Кроме того, lang_shift по умолчанию эмулирует 105-клавишную ISO клавиатуру (с кнопкой между LShift и Z), если по какой-то причине, вы хотите чтобы эмулировалась 104 ANSI клавиатура (которая без этой клавиши), добавьте в `config.h` следующую строку: 49 | 50 | ``` 51 | #define LANG_USE_ANSI 52 | ``` 53 | 54 | ## Задание SAFE_RANGE 55 | 56 | Для пользовательских кейкодов существует такое понятие как `SAFE_RANGE`, обычно он используется для того чтобы после него помещать свои кейкоды, которые делают особые действия: 57 | 58 | ```c 59 | enum custom_keycodes { 60 | MY_KEY1 = SAFE_RANGE, 61 | MY_KEY2, 62 | // ... 63 | } 64 | ``` 65 | 66 | Таким образом гарантируется что пользовательские кейкоды не пересекутся с системными кейкодами. Обычно для этого используется `SAFE_RANGE`. 67 | 68 | Но иногда случается так, что клавиатура добавляет своих кейкодов, и создаёт новую переменную SAFE_RANGE, которую называет по-другому, нужно за этим следить. Посмотрите, нету ли у вас такой особой переменной. Например, в Moonlander эта переменная называется `ML_SAFE_RANGE`. 69 | 70 | После того как вы найдёте имя этой переменной, в `keymap.c` прямо перед включением `lang_shift` нужно написать: 71 | 72 | ```c 73 | #define CUSTOM_SAFE_RANGE <ваша переменная> 74 | ``` 75 | 76 | Пример для Moonlander: 77 | ```c 78 | #define CUSTOM_SAFE_RANGE ML_SAFE_RANGE 79 | ``` 80 | 81 | Затем при использовании своих кейкодов необходимо указывать именно `CUSTOM_SAFE_RANGE`, потому что данный модуль использует необходимое количество кейкодов из пользовательских кейкодов и переопределяет эту переменную. Пример: 82 | 83 | ```c 84 | enum custom_keycodes { 85 | KEYCODES_START = CUSTOM_SAFE_RANGE, 86 | 87 | // English specific keys 88 | EN_LTEQ, // <= 89 | // ... 90 | }; 91 | ``` 92 | 93 | ## Подключить код 94 | 95 | В своём файле `keymap.c` в самом верху подключаем файл `lang_shift/include.h`: 96 | ```c 97 | #include "lang_shift/include.h" 98 | ``` 99 | 100 | ## Вызов модуля для каждой клавиши 101 | 102 | В своей функции `process_record_user` включаете добавляете следующую обработку: 103 | 104 | ```c 105 | bool process_record_user(uint16_t key, keyrecord_t *record) { 106 | if (!lang_shift_process_record(keycode, record)) 107 | return false; 108 | 109 | ... 110 | } 111 | ``` 112 | 113 | Затем нужно определить функцию `matrix_scan_user`, если она у вас ещё не определена, и вызывать функцию `user_timer`, а в этой функции вызывать `lang_shift_user_timer();`: 114 | ```c 115 | void user_timer(void) { 116 | lang_shift_user_timer(); 117 | } 118 | 119 | void matrix_scan_user(void) { 120 | user_timer(); 121 | } 122 | ``` 123 | 124 | **Объяснение:** функция `matrix_scan_user` вызывается примерно каждые 2 миллисекунды, она сканирует матрицу. Значит её вполне можно использовать для отслеживания собственных таймеров. Поэтому мы вызываем из неё функцию `user_timer`, которая лучше говорит о наших намерениях, чем `matrix_scan_user`. А уже в функции `user_timer` мы вызываем обработку случая автоматического отжатия шифта или обратного переключения языка 125 | 126 | ## Настройка раскладки 127 | 128 | Пишите свои раскладки следующим образом: 129 | * Первая раскладка без зажатого шифта находится в слое 0 130 | * Первая раскладка при зажатом шифте находится в слое 1 131 | * Аналогично вторая без шифта в 2 слое 132 | * Аналогично вторая с шифтом в 3 слое 133 | 134 | Причём в шифтовом слое для всех буквенных символов надо прописывать что будет нажат шифт. 135 | 136 | А для шифта и переключения языка используете кейкоды `SFT_N`, `LA_CHNG`, etc. 137 | 138 | Если вы будете использовать обычный шифт вместо `SFT_N` или `SFT_N_O`, то у вас в шифтомом слое не будут работать цифры, маленькие буквы и некоторые препинаки. Данные шифты отслеживают когда были нажаты и специально для кейкода цифр, например, отжимают шифт на время их нажатия. Поэтому обычный кейкод `KC_LSFT` использовать нельзя. 139 | 140 | Для английского и русского языка используйте кейкоды с префиксами `EN_*`, `RU_*`. Для препинаков, которые не зависят от языка, используйте Agnostic кейкоды `AG_*`. 141 | 142 | Для модификаторов используйте клавиши `CTRL_0`, `ALT_0`, etc. Они включают 0-й слой с английским языком, чтобы все хоткеи нажимались исключительно на английской раскладке. 143 | 144 | Все кейкоды смотрите в файле `include.h`, у некоторых кейкодов там подробное описание. 145 | 146 | Пример очень маленькой раскладки для клавиатуры с 4 кнопками 147 | 148 | ```c 149 | const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { 150 | //--------------------------------------------------------------------------- 151 | [0] = LAYOUT(EN_AMPR, EN_W, SFT_N, LA_CHNG), // & w <Шифт> <Переключение языка> 152 | [1] = LAYOUT(EN_7, EN_S_W, _______, RU_NUME), // 7 W <Шифт> № 153 | [2] = LAYOUT(RU_7 , RU_JU, SFT_N, LA_CHNG), // 7 ю <Шифт> <Переключение языка> 154 | [3] = LAYOUT(RU_QUES, RU_S_JU, _______, EN_GRV ), // ? Ю <Шифт> ` 155 | }; 156 | ``` 157 | 158 | Обратите внимание, что в шифтовом слое обязательно нужно прописывать что под `ю` находится `Ю` с зажатым шифтом. 159 | 160 | # Рассинхрон 161 | 162 | Это работает на основе того что клавиатура внутри хранит какой сейчас язык на компьютере. Затем при использовании кейкода `LA_CHNG` одновременно отправляется запрос на переключение языка в компьютер и язык переключается в клавиатуре. Но часто бывает так что в компьютере язык поменялся не из-за клавиатуры. Поэтому приходится вручную говорить клавиатуре об этом. Для этого есть кейкод `SYNC_LA`. 163 | 164 | # Once Shift 165 | 166 | Здесь так же представлены клавиши `SFT_N_O`, `SFT_1_O`, `SFT_3_O` (SHiFt Once), которые работают следующим образом: 167 | * При зажатии применяют шифт только к одной следующей клавише, то есть он автоматически выключается после срабатывания на одной клавише. 168 | * При одиночном нажатии тоже срабатывает для следующей клавиши, как One Shot Shift, но без таймаута. 169 | 170 | Такая клавиша может пригодиться если вы печатаете с большой скоростью и вам сложно выдерживать время удержания шифта, и у вас получается что-то вроде "ПРивет" или "привет" (шифт отпустился раньше начала следующего слова). 171 | 172 | Так же существует клавиша `AG_SDOT`, которая отправляет: точку, пробел и применяет шифт к следующей клавише. Идеальна для использования при наборе текста. 173 | 174 | # Плюсы и минусы по сравнению с заданием раскладки в ОС 175 | 176 | **Плюсы:** 177 | * Будет без всякой настройки компьютера работать, если уже установлены стандартные русский и английский языки. Вставил, нажал кнопку выбора метода переключения языка и всё. 178 | * Будет работать без прав администратора. 179 | * Будет работать на телефоне (на телефоне обычно очень сложно настраивать раскладку подключаемой физической клавиатуры, а на apple телефонах так вообще вроде невозможно). 180 | 181 | **Минусы:** 182 | * Невозможно посылать кейкоды, не существующие ни в русской, ни в английской раскладке, например `« » —`. 183 | * Необходимость компилировать свою раскладку из кода, и вообще притрагиваться к коду. Некоторые люди предпочитают работать исключительно в графических конфигураторах. 184 | * Ввод английских символов при включённой русской раскладке может тормозить из-за тормознутого метода переключения раскладки. Из-за этого же иногда некоторые клавиши могут прожиматься не на том языке. 185 | 186 | # Использование в коде 187 | 188 | Всё что вы можете использовать в коде можно посмотреть в файле `include.h`. 189 | 190 | ## Обычная клавиша на шифтовом слое 191 | 192 | Если вы хотите использовать кастомный кейкод на шифтовом слое, но чтобы он был без зажатого шифта, то нужно в коде этого кейкода перед его активацией написать: `shift_activate(0);`. Данный код вырубает шифт, но таким образом что шифт потом сам включится через 200мс, или если он понадобится другой клавише. 193 | 194 | Пример использования: 195 | ```c 196 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { 197 | switch (keycode) { 198 | case CT_Y: 199 | if (record->event.pressed) { 200 | shift_activate(0); 201 | register_code(KC_LCTRL); 202 | register_code(KC_Y); 203 | unregister_code(KC_Y); 204 | unregister_code(KC_LCTRL); 205 | } 206 | return false; 207 | break; 208 | } 209 | return true; 210 | } 211 | ``` 212 | 213 | ## Англоязычный хоткей 214 | 215 | Если вы хотите чтобы перед нажатием вашего хоткея был включён английский язык, то можно перед его нажатием вызывать функцию `lang_activate(0);`. Это будет работать аналогично верхнему пункту. 216 | 217 | Пример использования: 218 | ```c 219 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { 220 | switch (keycode) { 221 | case CT_D: 222 | if (record->event.pressed) { 223 | lang_activate(0); 224 | register_code(KC_LCTRL); 225 | register_code(KC_D); 226 | unregister_code(KC_D); 227 | unregister_code(KC_LCTRL); 228 | } 229 | return false; 230 | break; 231 | } 232 | return true; 233 | } 234 | ``` 235 | 236 | ## Набрать символ на нужном языке 237 | 238 | Если вы хотите набрать символ из определённого языка, при этом не зная какой включен сейчас, можно использовать функцию `lang_shift_tap_key`. Аналогично можно использовать функцию `lang_shift_press_key` если вам нужно этот символ зажать или отжать. 239 | 240 | Пример использования: 241 | ```c 242 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { 243 | switch (keycode) { 244 | case EN_LTEQ: 245 | if (record->event.pressed) { 246 | lang_shift_tap_key(EN_LT); 247 | lang_shift_tap_key(AG_EQL); 248 | } 249 | return false; 250 | break; 251 | } 252 | return true; 253 | } 254 | ``` 255 | 256 | ## Применить шифт к следующей клавише 257 | 258 | Например, вы хотите создать клавишу `AG_QSSP`, которая нажимает: <вопросительный знак> + <пробел> + <применить шифт к следующей клавише>, то вы можете использовать функцию `shift_once_use_to_next_key`, где в параметрах нужно указать на какой слой нужно переключить следующую клавишу. Если это русский язык, то нужно указать 3; если английский - 0; если вы хотите чтобы оно автоматически подстраивалось под ваш язык, то можно вызвать её с другой функцией: `shift_once_use_to_next_key(lang_get_shift_layer_number())`. В итоге код получится такой: 259 | 260 | ```c 261 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { 262 | switch (keycode) { 263 | case AG_QSSP: 264 | if (record->event.pressed) { 265 | lang_shift_tap_key(AG_QUES); 266 | register_code(KC_SPC); 267 | unregister_code(KC_SPC); 268 | shift_once_use_to_next_key(lang_get_shift_layer_number()); 269 | } 270 | return false; 271 | break; 272 | } 273 | return true; 274 | } 275 | ``` 276 | 277 | # Программное отслеживание 278 | 279 | [Здесь](https://gist.github.com/Onefabis/d8d27f8c02d03071f1d987c76686cbd8) находится питон-скрипт, который работает только для Windows, и синхронизирует язык в системе с языком в клавиатуре. Может быть полезно если вы хотите иметь разный язык для разных окон или если у вас часто происходит рассинхрон и он раздражает. Там же находится инструкция по установке в клавиатуру и в систему. 280 | 281 | # Changelog 282 | 283 | **25.01.2021:** 284 | * Теперь клавиши `CTRL_0` итд только включают 0-й слой при их зажатии. Клавиши же `CTRL_EN` итд не только включают 0-й слой, но и переключают язык на английский. 285 | 286 | **23.01.2021:** 287 | * Пофикшен баг, при котором, если зажать `&`, который находится в шифтовом слое, то после отжатия шифта продолжает набираться `7`: `<& down>&&&&&&&&&&&&77777777777<& up>`, теперь набирается нормально: `<& down>&&&&&&&&&&&&&&&&&&&&&&&<& up>`. 288 | -------------------------------------------------------------------------------- /src.c: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------- 2 | // Работа с шифтом 3 | // --------------------------------------------------------------------------- 4 | 5 | Shift shift_should_be = 0; 6 | Shift shift_current = 0; 7 | uint32_t shift_timer = 0; 8 | uint8_t shift_pressed_count = 0; 9 | 10 | Key shift_get_key(Key key) { 11 | switch (key) { 12 | case KS_GRV: return KC_GRV; 13 | case KS_TILD: return KC_GRV; 14 | case KS_1: return KC_1; 15 | case KS_EXCL: return KC_1; 16 | case KS_2: return KC_2; 17 | case KS_AT: return KC_2; 18 | case KS_3: return KC_3; 19 | case KS_HASH: return KC_3; 20 | case KS_4: return KC_4; 21 | case KS_DLR: return KC_4; 22 | case KS_5: return KC_5; 23 | case KS_PERC: return KC_5; 24 | case KS_6: return KC_6; 25 | case KS_CIRC: return KC_6; 26 | case KS_7: return KC_7; 27 | case KS_AMPR: return KC_7; 28 | case KS_8: return KC_8; 29 | case KS_ASTR: return KC_8; 30 | case KS_9: return KC_9; 31 | case KS_LPRN: return KC_9; 32 | case KS_0: return KC_0; 33 | case KS_RPRN: return KC_0; 34 | case KS_MINS: return KC_MINS; 35 | case KS_UNDS: return KC_MINS; 36 | case KS_EQL: return KC_EQL; 37 | case KS_PLUS: return KC_EQL; 38 | case KS_Q: return KC_Q; 39 | case KS_S_Q: return KC_Q; 40 | case KS_W: return KC_W; 41 | case KS_S_W: return KC_W; 42 | case KS_E: return KC_E; 43 | case KS_S_E: return KC_E; 44 | case KS_R: return KC_R; 45 | case KS_S_R: return KC_R; 46 | case KS_T: return KC_T; 47 | case KS_S_T: return KC_T; 48 | case KS_Y: return KC_Y; 49 | case KS_S_Y: return KC_Y; 50 | case KS_U: return KC_U; 51 | case KS_S_U: return KC_U; 52 | case KS_I: return KC_I; 53 | case KS_S_I: return KC_I; 54 | case KS_O: return KC_O; 55 | case KS_S_O: return KC_O; 56 | case KS_P: return KC_P; 57 | case KS_S_P: return KC_P; 58 | case KS_LBRC: return KC_LBRC; 59 | case KS_LCBR: return KC_LBRC; 60 | case KS_RBRC: return KC_RBRC; 61 | case KS_RCBR: return KC_RBRC; 62 | case KS_A: return KC_A; 63 | case KS_S_A: return KC_A; 64 | case KS_S: return KC_S; 65 | case KS_S_S: return KC_S; 66 | case KS_D: return KC_D; 67 | case KS_S_D: return KC_D; 68 | case KS_F: return KC_F; 69 | case KS_S_F: return KC_F; 70 | case KS_G: return KC_G; 71 | case KS_S_G: return KC_G; 72 | case KS_H: return KC_H; 73 | case KS_S_H: return KC_H; 74 | case KS_J: return KC_J; 75 | case KS_S_J: return KC_J; 76 | case KS_K: return KC_K; 77 | case KS_S_K: return KC_K; 78 | case KS_L: return KC_L; 79 | case KS_S_L: return KC_L; 80 | case KS_SCLN: return KC_SCLN; 81 | case KS_COLN: return KC_SCLN; 82 | case KS_QUOT: return KC_QUOT; 83 | case KS_DQUO: return KC_QUOT; 84 | #ifdef LANG_USE_ANSI 85 | case KS_BSLS: return KC_BSLS; 86 | case KS_PIPE: return KC_BSLS; 87 | #else 88 | case KS_BSLS: return KC_NUHS; // ISO keyboards use another backslash code 89 | case KS_PIPE: return KC_NUHS; // but there is no difference for OS 90 | case KS_LT2: return KC_NUBS; 91 | case KS_GT2: return KC_NUBS; 92 | #endif 93 | case KS_Z: return KC_Z; 94 | case KS_S_Z: return KC_Z; 95 | case KS_X: return KC_X; 96 | case KS_S_X: return KC_X; 97 | case KS_C: return KC_C; 98 | case KS_S_C: return KC_C; 99 | case KS_V: return KC_V; 100 | case KS_S_V: return KC_V; 101 | case KS_B: return KC_B; 102 | case KS_S_B: return KC_B; 103 | case KS_N: return KC_N; 104 | case KS_S_N: return KC_N; 105 | case KS_M: return KC_M; 106 | case KS_S_M: return KC_M; 107 | case KS_COMM: return KC_COMM; 108 | case KS_LT: return KC_COMM; 109 | case KS_DOT: return KC_DOT; 110 | case KS_GT: return KC_DOT; 111 | case KS_SLSH: return KC_SLSH; 112 | case KS_QUES: return KC_SLSH; 113 | 114 | default: return NONE_KEY; 115 | } 116 | } 117 | 118 | Shift shift_get_shift(Key key) { 119 | if (KS_GRV <= key && key <= KS_QUES) { 120 | return (key - KS_GRV) % 2; 121 | } else { 122 | return NONE_SHIFT; 123 | } 124 | } 125 | 126 | void shift_activate(Shift shift) { 127 | if (shift_current != shift) { 128 | shift_timer = timer_read(); 129 | if (shift) { 130 | register_code(KC_LSHIFT); 131 | } else { 132 | unregister_code(KC_LSHIFT); 133 | } 134 | } 135 | shift_current = shift; 136 | } 137 | 138 | void shift_activate_from_user(Shift shift) { 139 | shift_should_be = shift; 140 | shift_activate(shift); 141 | } 142 | 143 | Key shift_process(Key key, bool down) { 144 | Shift new_shift = shift_get_shift(key); 145 | if (down) { 146 | if (new_shift != NONE_SHIFT) { 147 | shift_activate(new_shift); 148 | } else { 149 | shift_activate(shift_should_be); 150 | } 151 | } 152 | 153 | if (new_shift != NONE_SHIFT) { 154 | if (down) { 155 | shift_pressed_count++; 156 | } else { 157 | shift_pressed_count--; 158 | } 159 | } 160 | 161 | return shift_get_key(key); 162 | } 163 | 164 | void shift_user_timer(void) { 165 | // Нужно выключать шифт после прохождения определённого времени, потому что пользователь ожидает как будто шифт на самом деле включён 166 | if (shift_pressed_count == 0 && shift_current != shift_should_be && timer_read() - shift_timer >= 100) { 167 | shift_activate(shift_should_be); 168 | shift_timer = timer_read(); 169 | } 170 | } 171 | 172 | // --------------------------------------------------------------------------- 173 | // Работа с одиночным шифтом 174 | // --------------------------------------------------------------------------- 175 | 176 | uint8_t shift_once_disable_stage = 2; 177 | uint8_t shift_once_layer_off = 0; 178 | uint8_t shift_once_layer_current = 0; 179 | uint32_t shift_once_enabled_time = 0; 180 | bool shift_once_can_disable = true; 181 | 182 | bool shift_once_is_enabled(void) { 183 | return shift_once_disable_stage == 2; 184 | } 185 | 186 | void shift_once_use_to_next_key(uint8_t layer) { 187 | if (shift_current == 0) { 188 | shift_activate_from_user(true); 189 | layer_on(layer); 190 | shift_once_disable_stage = 2; 191 | shift_once_layer_off = layer; 192 | shift_once_enabled_time = timer_read(); 193 | } 194 | } 195 | 196 | void shift_once_process_key(uint8_t layer, bool down) { 197 | if (down) { 198 | shift_once_use_to_next_key(layer); 199 | shift_once_can_disable = false; 200 | } else { 201 | shift_once_can_disable = true; 202 | shift_once_enabled_time = timer_read(); 203 | } 204 | } 205 | 206 | void shift_once_disable(void) { 207 | if (shift_once_disable_stage == 2) { 208 | layer_off(shift_once_layer_off); 209 | shift_activate_from_user(false); 210 | shift_once_disable_stage = 0; 211 | } 212 | } 213 | 214 | void shift_once_process(Key key, keyrecord_t* record) { 215 | bool down = record->event.pressed; 216 | 217 | if (shift_once_disable_stage == 1) { 218 | shift_once_disable_stage = 0; 219 | shift_activate_from_user(false); 220 | } 221 | if (down && key != SFT_N_O && shift_once_disable_stage == 2) { 222 | shift_once_disable_stage = 1; 223 | layer_off(shift_once_layer_off); 224 | } 225 | } 226 | 227 | void shift_once_user_timer(void) { 228 | if (shift_once_can_disable && shift_once_is_enabled() && timer_read() - shift_once_enabled_time >= 1000) { 229 | shift_once_disable(); 230 | } 231 | } 232 | 233 | // --------------------------------------------------------------------------- 234 | // Работа с языком 235 | // --------------------------------------------------------------------------- 236 | 237 | Lang lang_should_be = 0; 238 | Lang lang_current = 0; 239 | uint32_t lang_timer = 0; 240 | uint8_t lang_pressed_count = 0; 241 | 242 | Key lang_get_key(Key key) { 243 | if (EN_GRV <= key && key <= EN_QUES) { 244 | return (key - EN_GRV) + KS_GRV; 245 | } else if (RU_JO <= key && key <= RU_COMM) { 246 | return (key - RU_JO) + KS_GRV; 247 | } else { 248 | return NONE_KEY; 249 | } 250 | } 251 | 252 | Lang lang_get_lang(Key key) { 253 | if (EN_GRV <= key && key <= EN_QUES) { 254 | return 0; 255 | } else if (RU_JO <= key && key <= RU_COMM) { 256 | return 1; 257 | } else { 258 | return NONE_LANG; 259 | } 260 | } 261 | 262 | Key lang_calc_agnostic(Key key) { 263 | if (lang_current == 0) { 264 | switch (key) { 265 | case AG_1: return EN_1; 266 | case AG_2: return EN_2; 267 | case AG_3: return EN_3; 268 | case AG_4: return EN_4; 269 | case AG_5: return EN_5; 270 | case AG_6: return EN_6; 271 | case AG_7: return EN_7; 272 | case AG_8: return EN_8; 273 | case AG_9: return EN_9; 274 | case AG_0: return EN_0; 275 | case AG_EXCL: return EN_EXCL; 276 | case AG_PERC: return EN_PERC; 277 | case AG_ASTR: return EN_ASTR; 278 | case AG_LPRN: return EN_LPRN; 279 | case AG_RPRN: return EN_RPRN; 280 | case AG_MINS: return EN_MINS; 281 | case AG_UNDS: return EN_UNDS; 282 | case AG_EQL: return EN_EQL; 283 | case AG_PLUS: return EN_PLUS; 284 | case AG_SCLN: return EN_SCLN; 285 | case AG_COLN: return EN_COLN; 286 | case AG_DQUO: return EN_DQUO; 287 | case AG_BSLS: return EN_BSLS; 288 | #ifndef LANG_USE_ANSI 289 | case AG_PIPE: return EN_PIPE; 290 | #endif 291 | case AG_COMM: return EN_COMM; 292 | case AG_DOT: return EN_DOT; 293 | case AG_SLSH: return EN_SLSH; 294 | #ifndef LANG_USE_ANSI 295 | case AG_S_SL: return EN_SLSH; 296 | #endif 297 | case AG_QUES: return EN_QUES; 298 | default: return NONE_KEY; 299 | } 300 | } else { 301 | switch (key) { 302 | case AG_1: return RU_1; 303 | case AG_2: return RU_2; 304 | case AG_3: return RU_3; 305 | case AG_4: return RU_4; 306 | case AG_5: return RU_5; 307 | case AG_6: return RU_6; 308 | case AG_7: return RU_7; 309 | case AG_8: return RU_8; 310 | case AG_9: return RU_9; 311 | case AG_0: return RU_0; 312 | case AG_EXCL: return RU_EXCL; 313 | case AG_PERC: return RU_PERC; 314 | case AG_ASTR: return RU_ASTR; 315 | case AG_LPRN: return RU_LPRN; 316 | case AG_RPRN: return RU_RPRN; 317 | case AG_MINS: return RU_MINS; 318 | case AG_UNDS: return RU_UNDS; 319 | case AG_EQL: return RU_EQL; 320 | case AG_PLUS: return RU_PLUS; 321 | case AG_SCLN: return RU_SCLN; 322 | case AG_COLN: return RU_COLN; 323 | case AG_DQUO: return RU_DQUO; 324 | case AG_BSLS: return RU_BSLS; 325 | #ifndef LANG_USE_ANSI 326 | case AG_PIPE: return RU_PIPE; 327 | #endif 328 | case AG_COMM: return RU_COMM; 329 | case AG_DOT: return RU_DOT; 330 | #ifdef LANG_USE_ANSI 331 | case AG_SLSH: return RU_SLSH; 332 | #else 333 | case AG_SLSH: return RU_SLS2; 334 | case AG_S_SL: return RU_SLSH; 335 | #endif 336 | case AG_QUES: return RU_QUES; 337 | default: return NONE_KEY; 338 | } 339 | } 340 | } 341 | 342 | uint8_t lang_get_shift_layer_number(void) { 343 | return lang_should_be * 2 + 1; 344 | } 345 | 346 | void lang_synchronize(void) { 347 | lang_timer = timer_read(); 348 | switch (lang_current_change) { 349 | case LANG_CHANGE_CAPS: { 350 | // Костыль, потому что при нажатии Shift+Caps включается режим Caps, а не переключение языка :facepalm: 351 | if (shift_current == 1) { 352 | unregister_code(KC_LSHIFT); 353 | register_code(KC_CAPS); 354 | unregister_code(KC_CAPS); 355 | register_code(KC_LSHIFT); 356 | } else { 357 | register_code(KC_CAPS); 358 | unregister_code(KC_CAPS); 359 | } 360 | } break; 361 | case LANG_CHANGE_ALT_SHIFT: { 362 | register_code(KC_LALT); 363 | register_code(KC_LSHIFT); 364 | unregister_code(KC_LSHIFT); 365 | unregister_code(KC_LALT); 366 | 367 | // Костыль, потому что при зажатом шифте если хочется нажать клавишу, которая переключает язык, то шифт слетает... 368 | if (shift_current == 1) { 369 | register_code(KC_LSHIFT); 370 | } 371 | } break; 372 | case LANG_CHANGE_SHIFT_ALT: { 373 | register_code(KC_LSHIFT); 374 | register_code(KC_LALT); 375 | unregister_code(KC_LALT); 376 | unregister_code(KC_LSHIFT); 377 | 378 | // Костыль, потому что при зажатом шифте если хочется нажать клавишу, которая переключает язык, то шифт слетает... 379 | if (shift_current == 1) { 380 | register_code(KC_LSHIFT); 381 | } 382 | } break; 383 | case LANG_CHANGE_CTRL_SHIFT: { 384 | register_code(KC_LCTRL); 385 | register_code(KC_LSHIFT); 386 | unregister_code(KC_LSHIFT); 387 | unregister_code(KC_LCTL); 388 | 389 | // Костыль, потому что при зажатом шифте если хочется нажать клавишу, которая переключает язык, то шифт слетает... 390 | if (shift_current == 1) { 391 | register_code(KC_LSHIFT); 392 | } 393 | } break; 394 | case LANG_CHANGE_SHIFT_CTRL: { 395 | register_code(KC_LSHIFT); 396 | register_code(KC_LCTRL); 397 | unregister_code(KC_LCTL); 398 | unregister_code(KC_LSHIFT); 399 | 400 | // Костыль, потому что при зажатом шифте если хочется нажать клавишу, которая переключает язык, то шифт слетает... 401 | if (shift_current == 1) { 402 | register_code(KC_LSHIFT); 403 | } 404 | } break; 405 | case LANG_CHANGE_WIN_SPACE: { 406 | register_code(KC_LGUI); 407 | register_code(KC_SPACE); 408 | unregister_code(KC_SPACE); 409 | unregister_code(KC_LGUI); 410 | } break; 411 | } 412 | } 413 | 414 | void lang_activate(Lang lang) { 415 | // Нужно дополнять этот код, если нужно три языка и более 416 | if (lang_current != lang) { 417 | lang_synchronize(); 418 | } 419 | lang_current = lang; 420 | } 421 | 422 | void lang_activate_from_user(Lang lang) { 423 | lang_should_be = lang; 424 | lang_activate(lang); 425 | } 426 | 427 | void lang_activate_from_user_without_sync(Lang lang) { 428 | lang_should_be = lang; 429 | lang_current = lang; 430 | } 431 | 432 | Key lang_process(Key key, bool down) { 433 | Key after_agnostic = lang_calc_agnostic(key); 434 | if (after_agnostic != NONE_KEY) { 435 | key = after_agnostic; 436 | } 437 | 438 | Lang new_lang = lang_get_lang(key); 439 | if (down) { 440 | if (new_lang != NONE_LANG) { 441 | lang_activate(new_lang); 442 | } else { 443 | lang_activate(lang_should_be); 444 | } 445 | } 446 | 447 | if (new_lang != NONE_LANG) { 448 | if (down) { 449 | lang_pressed_count++; 450 | } else { 451 | lang_pressed_count--; 452 | } 453 | } 454 | 455 | return lang_get_key(key); 456 | } 457 | 458 | void lang_user_timer(void) { 459 | // Нужно выключать язык после прохождения определённого времени, потому что пользователь ожидает как будто шифт на самом деле включён 460 | if (lang_pressed_count == 0 && lang_current != lang_should_be && timer_read() - lang_timer >= 100) { 461 | lang_activate(lang_should_be); 462 | } 463 | } 464 | 465 | // --------------------------------------------------------------------------- 466 | // Обработка клавиш 467 | // --------------------------------------------------------------------------- 468 | 469 | uint8_t lang_shift_current_shift_layer = 0; 470 | 471 | void lang_shift_press_key(Key key, bool down) { 472 | keyrecord_t record = { 473 | .event = { 474 | .key = { 475 | .col = 0, 476 | .row = 0, 477 | }, 478 | .pressed = down, 479 | .time = timer_read(), 480 | }, 481 | }; 482 | 483 | lang_shift_process_record(key, &record); 484 | } 485 | 486 | void lang_shift_tap_key(Key key) { 487 | lang_shift_press_key(key, true); 488 | lang_shift_press_key(key, false); 489 | shift_activate(shift_should_be); 490 | lang_activate(lang_should_be); 491 | } 492 | 493 | bool lang_shift_process_custom_keycodes(Key key, keyrecord_t* record) { 494 | bool down = record->event.pressed; 495 | 496 | // Обрабатываем клавиши, связанные с кастомным шифтом и кастомным переключением языка 497 | switch (key) { 498 | case SFT_N_O: 499 | shift_once_process_key(lang_get_shift_layer_number(), down); 500 | return false; 501 | case SFT_N: 502 | if (down) { 503 | shift_activate_from_user(true); 504 | lang_shift_current_shift_layer = lang_get_shift_layer_number(); 505 | layer_on(lang_shift_current_shift_layer); 506 | } else { 507 | shift_should_be = false; 508 | if (shift_pressed_count == 0) { 509 | shift_activate_from_user(false); 510 | } 511 | layer_off(lang_shift_current_shift_layer); 512 | } 513 | return false; 514 | case LA_CHNG: 515 | if (down) { 516 | if (lang_should_be == 0) { 517 | lang_activate_from_user(1); 518 | layer_on(2); 519 | } else { 520 | lang_activate_from_user(0); 521 | layer_off(2); 522 | } 523 | } 524 | return false; 525 | case LA_SYNC: 526 | if (down) { 527 | lang_synchronize(); 528 | } 529 | return false; 530 | case LA_CAPS: 531 | if (down) { 532 | lang_current_change = LANG_CHANGE_CAPS; 533 | } 534 | return false; 535 | case LA_ALSH: 536 | if (down) { 537 | lang_current_change = LANG_CHANGE_ALT_SHIFT; 538 | } 539 | return false; 540 | case LA_SHAL: 541 | if (down) { 542 | lang_current_change = LANG_CHANGE_SHIFT_ALT; 543 | } 544 | return false; 545 | case LA_CTSH: 546 | if (down) { 547 | lang_current_change = LANG_CHANGE_CTRL_SHIFT; 548 | } 549 | return false; 550 | case LA_SHCT: 551 | if (down) { 552 | lang_current_change = LANG_CHANGE_SHIFT_CTRL; 553 | } 554 | return false; 555 | case LA_WISP: 556 | if (down) { 557 | lang_current_change = LANG_CHANGE_WIN_SPACE; 558 | } 559 | return false; 560 | case AG_3DOT: 561 | if (record->event.pressed) { 562 | lang_shift_tap_key(AG_DOT); 563 | lang_shift_tap_key(AG_DOT); 564 | lang_shift_tap_key(AG_DOT); 565 | } 566 | return false; 567 | break; 568 | case AG_CMSP: 569 | if (record->event.pressed) { 570 | lang_shift_tap_key(AG_COMM); 571 | register_code(KC_SPC); 572 | unregister_code(KC_SPC); 573 | } 574 | return false; 575 | break; 576 | case AG_SDOT: 577 | if (record->event.pressed) { 578 | lang_shift_tap_key(AG_DOT); 579 | register_code(KC_SPC); 580 | unregister_code(KC_SPC); 581 | shift_once_use_to_next_key(lang_get_shift_layer_number()); 582 | } 583 | return false; 584 | break; 585 | } 586 | 587 | return true; 588 | } 589 | 590 | bool lang_shift_process_english_modifiers(Key key, keyrecord_t* record) { 591 | static Lang lang_stack[3] = {}; 592 | static uint8_t modifiers_count = 0; 593 | #define PROCESS(NAME, REGISTER, UNREGISTER, ACTIVATE_LANG) \ 594 | case NAME: { \ 595 | if (record->event.pressed) { \ 596 | lang_stack[modifiers_count] = lang_should_be; \ 597 | modifiers_count += 1; \ 598 | if (lang_should_be == 1) { \ 599 | layer_off(2); \ 600 | } \ 601 | if (ACTIVATE_LANG) { \ 602 | lang_activate_from_user(0); \ 603 | } else { \ 604 | lang_activate_from_user_without_sync(0); \ 605 | } \ 606 | REGISTER; \ 607 | } else { \ 608 | UNREGISTER; \ 609 | modifiers_count -= 1; \ 610 | if (ACTIVATE_LANG) { \ 611 | lang_activate_from_user(lang_stack[modifiers_count]); \ 612 | } else { \ 613 | lang_activate_from_user_without_sync(lang_stack[modifiers_count]); \ 614 | } \ 615 | if (lang_should_be == 1) { \ 616 | layer_on(2); \ 617 | } \ 618 | } \ 619 | return false; \ 620 | } break; 621 | 622 | #define Rg(x) register_code(KC_L ## x) 623 | #define Un(x) unregister_code(KC_L ## x) 624 | 625 | switch (key) { 626 | PROCESS(CTRL_0, Rg(CTRL), Un(CTRL), false); 627 | PROCESS(ALT_0, Rg(ALT), Un(ALT), false); 628 | PROCESS(WIN_0, Rg(GUI), Un(GUI), false); 629 | PROCESS(CTAL_0, { Rg(CTRL); Rg(ALT); }, { Un(ALT); Un(CTRL); }, false); 630 | PROCESS(SHAL_0, { Rg(SHIFT); Rg(ALT); }, { Un(ALT); Un(SHIFT); }, false); 631 | PROCESS(CTSH_0, { Rg(CTRL); Rg(SHIFT); }, { Un(SHIFT); Un(CTRL); }, false); 632 | PROCESS(MCAS_0, { Rg(CTRL); Rg(ALT); Rg(SHIFT); }, { Un(SHIFT); Un(ALT); Un(CTRL); }, false); 633 | 634 | PROCESS(CTRL_EN, Rg(CTRL), Un(CTRL), true); 635 | PROCESS(ALT_EN, Rg(ALT), Un(ALT), true); 636 | PROCESS(WIN_EN, Rg(GUI), Un(GUI), true); 637 | PROCESS(CTAL_EN, { Rg(CTRL); Rg(ALT); }, { Un(ALT); Un(CTRL); }, true); 638 | PROCESS(SHAL_EN, { Rg(SHIFT); Rg(ALT); }, { Un(ALT); Un(SHIFT); }, true); 639 | PROCESS(CTSH_EN, { Rg(CTRL); Rg(SHIFT); }, { Un(SHIFT); Un(CTRL); }, true); 640 | PROCESS(MCAS_EN, { Rg(CTRL); Rg(ALT); Rg(SHIFT); }, { Un(SHIFT); Un(ALT); Un(CTRL); }, true); 641 | } 642 | 643 | return true; 644 | } 645 | 646 | bool lang_shift_process_record(Key key, keyrecord_t* record) { 647 | // Обрабатываем Once Shift 648 | shift_once_process(key, record); 649 | 650 | bool down = record->event.pressed; 651 | 652 | // Разбираемся, имеет ли эта клавиша какой-то язык, заданный в ней 653 | Key key1 = lang_process(key, down); 654 | Key key_to_shift = key; 655 | if (key1 != NONE_KEY) { 656 | key_to_shift = key1; 657 | } 658 | 659 | // Разбираемся, имеет ли эта клавиша шифт, засунутый в неё 660 | // Это нужно отдельно от обработки языка, чтобы шифт мог выключаться для обычных клавиш 661 | Key key2 = shift_process(key_to_shift, down); 662 | if (key2 != NONE_KEY) { 663 | if (down) { 664 | register_code(key2); 665 | } else { 666 | unregister_code(key2); 667 | } 668 | return false; 669 | } 670 | 671 | if (!lang_shift_process_custom_keycodes(key, record)) { 672 | return false; 673 | } 674 | 675 | if (!lang_shift_process_english_modifiers(key, record)) { 676 | return false; 677 | } 678 | 679 | return true; 680 | } 681 | 682 | void lang_shift_user_timer(void) { 683 | shift_user_timer(); 684 | shift_once_user_timer(); 685 | lang_user_timer(); 686 | } 687 | --------------------------------------------------------------------------------