From 73fff1c9eea7a066aac918f640aca9922d289e89 Mon Sep 17 00:00:00 2001 From: Jonas Hietala Date: Mon, 30 Oct 2023 11:59:40 +0100 Subject: [PATCH] Working layout... --- keyboards/ferris/keymaps/treeman/casemodes.c | 230 +++++ keyboards/ferris/keymaps/treeman/casemodes.h | 26 + keyboards/ferris/keymaps/treeman/combos.def | 109 +++ keyboards/ferris/keymaps/treeman/config.h | 67 ++ keyboards/ferris/keymaps/treeman/inject.h | 20 + keyboards/ferris/keymaps/treeman/keycodes.h | 108 +++ keyboards/ferris/keymaps/treeman/keymap.c | 860 ++++++++++++++++++ keyboards/ferris/keymaps/treeman/layermodes.c | 74 ++ keyboards/ferris/keymaps/treeman/layermodes.h | 15 + keyboards/ferris/keymaps/treeman/leader.c | 135 +++ keyboards/ferris/keymaps/treeman/leader.h | 51 ++ keyboards/ferris/keymaps/treeman/oneshot.c | 70 ++ keyboards/ferris/keymaps/treeman/oneshot.h | 39 + keyboards/ferris/keymaps/treeman/repeat.c | 82 ++ keyboards/ferris/keymaps/treeman/repeat.h | 11 + keyboards/ferris/keymaps/treeman/roll.c | 60 ++ keyboards/ferris/keymaps/treeman/roll.h | 14 + keyboards/ferris/keymaps/treeman/rules.mk | 34 + keyboards/ferris/keymaps/treeman/status.h | 7 + keyboards/ferris/keymaps/treeman/tap_hold.c | 59 ++ keyboards/ferris/keymaps/treeman/tap_hold.h | 20 + users/muppetjones/features/casemodes.c | 1 + 22 files changed, 2092 insertions(+) create mode 100644 keyboards/ferris/keymaps/treeman/casemodes.c create mode 100644 keyboards/ferris/keymaps/treeman/casemodes.h create mode 100644 keyboards/ferris/keymaps/treeman/combos.def create mode 100644 keyboards/ferris/keymaps/treeman/config.h create mode 100644 keyboards/ferris/keymaps/treeman/inject.h create mode 100644 keyboards/ferris/keymaps/treeman/keycodes.h create mode 100644 keyboards/ferris/keymaps/treeman/keymap.c create mode 100644 keyboards/ferris/keymaps/treeman/layermodes.c create mode 100644 keyboards/ferris/keymaps/treeman/layermodes.h create mode 100644 keyboards/ferris/keymaps/treeman/leader.c create mode 100644 keyboards/ferris/keymaps/treeman/leader.h create mode 100644 keyboards/ferris/keymaps/treeman/oneshot.c create mode 100644 keyboards/ferris/keymaps/treeman/oneshot.h create mode 100644 keyboards/ferris/keymaps/treeman/repeat.c create mode 100644 keyboards/ferris/keymaps/treeman/repeat.h create mode 100644 keyboards/ferris/keymaps/treeman/roll.c create mode 100644 keyboards/ferris/keymaps/treeman/roll.h create mode 100644 keyboards/ferris/keymaps/treeman/rules.mk create mode 100644 keyboards/ferris/keymaps/treeman/status.h create mode 100644 keyboards/ferris/keymaps/treeman/tap_hold.c create mode 100644 keyboards/ferris/keymaps/treeman/tap_hold.h diff --git a/keyboards/ferris/keymaps/treeman/casemodes.c b/keyboards/ferris/keymaps/treeman/casemodes.c new file mode 100644 index 0000000000..1f1e296442 --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/casemodes.c @@ -0,0 +1,230 @@ +/* Copyright 2021 Andrew Rae ajrae.nv@gmail.com @andrewjrae + * + * 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 . + */ + +#include "casemodes.h" +#include "status.h" +#include "keycodes.h" + +/* The caps word concept started with @iaap on splitkb.com discord. + * However it has been implemented and extended by many splitkb.com users: + * - @theol0403 made many improvements to initial implementation + * - @precondition used caps lock rather than shifting + * - @dnaq his own implementation which also used caps lock + * - @sevanteri added underscores on spaces + * - @metheon extended on @sevanteri's work and added specific modes for + * snake_case and SCREAMING_SNAKE_CASE + * - @baffalop came up with the idea for xcase, which he implements in his own + * repo, however this is implemented by @iaap with support also for one-shot-shift. + * - @sevanteri + * - fixed xcase waiting mode to allow more modified keys and keys from other layers. + * - Added @baffalop's separator defaulting on first keypress, with a + * configurable default separator and overrideable function to determine + * if the default should be used. + */ + +#ifndef DEFAULT_XCASE_SEPARATOR +# define DEFAULT_XCASE_SEPARATOR KC_UNDS +#endif + +#define IS_OSM(keycode) (keycode >= QK_ONE_SHOT_MOD && keycode <= QK_ONE_SHOT_MOD_MAX) + +// enum for the xcase states +enum xcase_state { + XCASE_OFF = 0, // xcase is off + XCASE_ON, // xcase is actively on + XCASE_WAIT, // xcase is waiting for the delimiter input +}; + +// bool to keep track of the caps word state +static bool caps_word_on = false; + +// enum to keep track of the xcase state +static enum xcase_state xcase_state = XCASE_OFF; +// the keycode of the xcase delimiter +static uint16_t xcase_delimiter; + +// Check whether caps word is on +bool caps_word_enabled(void) { + return caps_word_on; +} + +// Enable caps word +void enable_caps_word(void) { + caps_word_on = true; + if (!host_keyboard_led_state().caps_lock) { + tap_code(is_caps_swapped() ? KC_ESC : KC_CAPS); + } +} + +// Disable caps word +void disable_caps_word(void) { + caps_word_on = false; + if (host_keyboard_led_state().caps_lock) { + tap_code(is_caps_swapped() ? KC_ESC : KC_CAPS); + } +} + +// Toggle caps word +void toggle_caps_word(void) { + if (caps_word_on) { + disable_caps_word(); + } else { + enable_caps_word(); + } +} + +// Check whether xcase is on +bool xcase_enabled(void) { + return xcase_state == XCASE_ON; +} + +// Check whether xcase is waiting for a keypress +bool xcase_waiting(void) { + return xcase_state == XCASE_WAIT; +} + +// Enable xcase and pickup the next keystroke as the delimiter +void enable_xcase(void) { + xcase_state = XCASE_WAIT; +} + +// Enable xcase with the specified delimiter +void enable_xcase_with(uint16_t delimiter) { + xcase_state = XCASE_ON; + xcase_delimiter = delimiter; +} + +// Disable xcase +void disable_xcase(void) { + xcase_state = XCASE_OFF; +} + +// Place the current xcase delimiter +static void place_delimiter(void) { + switch (xcase_delimiter) { + case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX: { + // apparently set_oneshot_mods() is dumb and doesn't deal with handedness for you + uint8_t mods = xcase_delimiter & 0x10 ? (xcase_delimiter & 0x0F) << 4 : xcase_delimiter & 0xFF; + set_oneshot_mods(mods); + break; + } + default: + tap_code16(xcase_delimiter); + break; + } +} + +// overrideable function to determine whether the case mode should stop +__attribute__((weak)) bool terminate_case_modes(uint16_t keycode, const keyrecord_t *record) { + switch (keycode) { + // Keycodes to ignore (don't disable caps word) + case KC_A ... KC_Z: + case KC_1 ... KC_0: + case KC_MINS: + case KC_UNDS: + case KC_BSPC: + // If mod chording disable the mods + if (record->event.pressed && (get_mods() != 0)) { + return true; + } + break; + default: + if (record->event.pressed) { + return true; + } + break; + } + return false; +} + +/* overrideable function to determine whether to use the default separator on + * first keypress when waiting for the separator. */ +__attribute__((weak)) bool use_default_xcase_separator(uint16_t keycode, const keyrecord_t *record) { + // for example: + /* switch (keycode) { */ + /* case KC_A ... KC_Z: */ + /* case KC_1 ... KC_0: */ + /* return true; */ + /* } */ + return false; +} + +bool process_case_modes(uint16_t keycode, const keyrecord_t *record) { + if (xcase_state == XCASE_WAIT) { + // grab the next input to be the delimiter + if (use_default_xcase_separator(keycode, record)) { + enable_xcase_with(DEFAULT_XCASE_SEPARATOR); + } else if (record->event.pressed) { + if (keycode > QK_MODS_MAX || IS_MODIFIER_KEYCODE(keycode)) { + // let special keys and normal modifiers go through + return true; + } else { + // factor in mods + if (get_mods() & MOD_MASK_SHIFT) { + keycode = LSFT(keycode); + } else if (get_mods() & MOD_BIT(KC_RALT)) { + keycode = RALT(keycode); + } + enable_xcase_with(keycode); + return false; + } + return false; + } else { + if (IS_OSM(keycode)) { + // this catches the OSM release if no other key was pressed + set_oneshot_mods(0); + enable_xcase_with(keycode); + } + // let other special keys go through + return true; + } + } + + if (caps_word_on || xcase_state) { + // Get the base keycode of a mod or layer tap key + switch (keycode) { + case QK_MOD_TAP ... QK_MOD_TAP_MAX: + case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: + case QK_TAP_DANCE ... QK_TAP_DANCE_MAX: + // Earlier return if this has not been considered tapped yet + if (record->tap.count == 0) return true; + keycode = keycode & 0xFF; + break; + default: + break; + } + + if (record->event.pressed) { + // handle xcase mode + if (xcase_state == XCASE_ON) { + // place the delimiter if needed + if (keycode == XCASE_DELIMITER_KEY) { + place_delimiter(); + return false; + } + } // end XCASE_ON + + // check if the case modes have been terminated + if (terminate_case_modes(keycode, record)) { + disable_caps_word(); + disable_xcase(); + } + } // end if event.pressed + + return true; + } + return true; +} diff --git a/keyboards/ferris/keymaps/treeman/casemodes.h b/keyboards/ferris/keymaps/treeman/casemodes.h new file mode 100644 index 0000000000..bbd1fe2d8e --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/casemodes.h @@ -0,0 +1,26 @@ +#pragma once + +#include QMK_KEYBOARD_H + +// Check whether caps word is on +bool caps_word_enabled(void); +// Enable caps word +void enable_caps_word(void); +// Disable caps word +void disable_caps_word(void); +// Toggle caps word +void toggle_caps_word(void); + +// Check whether xcase is on +bool xcase_enabled(void); +// Check whether xcase is waiting for a keypress +bool xcase_waiting(void); +// Enable xcase and pickup the next keystroke as the delimiter +void enable_xcase(void); +// Enable xcase with the specified delimiter +void enable_xcase_with(uint16_t delimiter); +// Disable xcase +void disable_xcase(void); + +// Function to be put in process user +bool process_case_modes(uint16_t keycode, const keyrecord_t *record); diff --git a/keyboards/ferris/keymaps/treeman/combos.def b/keyboards/ferris/keymaps/treeman/combos.def new file mode 100644 index 0000000000..e71a5ff87e --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/combos.def @@ -0,0 +1,109 @@ +// Left top row +COMB(q_comb, SE_Q, SE_J, SE_C) +COMB(qu_comb, QU, SE_C, SE_Y) +COMB(z_comb, SE_Z, SE_Y, SE_F) +COMB(ctrl_w, C(SE_W), SE_C, SE_Y, SE_F) +COMB(clear, CLEAR, SE_F, SE_P) +// Right top row +SUBS(el_str_int, "#{}"SS_TAP(X_LEFT), SE_X, SE_W) +COMB(eql, SE_EQL, SE_W, SE_O) +COMB(backsp, KC_BSPC, SE_O, SE_U) +COMB(ctrl_w2, C(SE_W), SE_W, SE_O, SE_U) +COMB(rev_rep, REV_REP, SE_U, SE_DOT) + +// Left home row +COMB(ctrl_combo_l, OS_CTRL, SE_S, SE_T) +COMB(escape_sym, ESC_SYM, SE_T, SE_H) +COMB(dquo, SE_DQUO, SE_S, SE_H) +COMB(tab_mod, TAB_MOD, SE_S, SE_T, SE_H) +COMB(del, KC_DEL, SE_H, SE_K) +// Right home row +COMB(coln_sym, COLN_SYM, SE_N, SE_A) +COMB(ctrl_combo_r, OS_CTRL, SE_A, SE_I) +COMB(quot, SE_QUOT, SE_N, SE_I) +COMB(ent, KC_ENT, SE_N, SE_A, SE_I) +COMB(save_vim, SAVE_VIM, SE_N, SE_A, SE_I, REPEAT) +// Mixed +COMB(capsword, CAPSWORD, SE_T, SE_A) + +// Left bottom row +COMB(vsp, VIM_VS, SE_V, SE_G) +COMB(gui_combo_l, OS_GUI, SE_G, SE_D) +COMB(lalt, MY_LALT, SE_V, SE_D) +COMB(close_win, CLOSE_WIN, SE_V, SE_G, SE_D) +COMB(shift_combo_l, KC_LSFT, SE_D, SE_B) +// Right bottom row +COMB(shift_combo_r, KC_RSFT, SE_SLSH, SE_L) +COMB(gui_combo_r, OS_GUI, SE_L, SE_LPRN) +COMB(dlr, SE_DLR, SE_LPRN, SE_RPRN) +COMB(leader, LEADER, SE_L, SE_RPRN) +COMB(swe, TG_SWE, SE_L, SE_LPRN, SE_RPRN) + +// Mixed home row + other +COMB(scln, SE_SCLN, SE_T, SE_D) +COMB(win_alt, WIN_ALT, SE_L, SE_A) + +// Vertical top left +SUBS(small_left_arrow, "<-", SE_Y, SE_T) +SUBS(lt_eq, "<=", SE_F, SE_H) +// Vertical top right +SUBS(large_right_arrow, "=>", SE_W, SE_N) +SUBS(small_right_arrow, "->", SE_O, SE_A) +SUBS(pipe_to, "|>", SE_U, SE_I) +// Vertical bottom left +COMB(sp, VIM_SP, SE_S, SE_V) +SUBS(gt_eq, ">=", SE_H, SE_D) + +// Thumbs +COMB(num, NUMWORD, MT_SPC, SE_E) + +// Numbers with cross side thumb +COMB(comb_6, SE_6, SE_E, SE_R) +COMB(comb_4, SE_4, SE_E, SE_S) +COMB(comb_0, SE_0, SE_E, SE_T) +COMB(comb_2, SE_2, SE_E, SE_H) +COMB(comb_8, SE_8, SE_E, SE_D) +COMB(comb_9, SE_9, MT_SPC, SE_L) +COMB(comb_3, SE_3, MT_SPC, SE_N) +COMB(comb_1, SE_1, MT_SPC, SE_A) +COMB(comb_5, SE_5, MT_SPC, SE_I) +COMB(comb_7, SE_7, MT_SPC, REPEAT) +// é +COMB(comb_e_acut, E_ACUT, SE_E, SE_K) + +// Symbols with same side thumb +// Upper +COMB(comb_tild, TILD, MT_SPC, SE_J) +COMB(comb_plus, SE_PLUS, MT_SPC, SE_C) +COMB(comb_astr, SE_ASTR, MT_SPC, SE_Y) +COMB(comb_exlm, SE_EXLM, MT_SPC, SE_F) +COMB(comb_hash, SE_HASH, SE_E, SE_W) +COMB(comb_at, SE_AT, SE_E, SE_O) +COMB(comb_circ, CIRC, SE_E, SE_U) +// Home-row +COMB(comb_pipe, SE_PIPE, MT_SPC, SE_R) +COMB(comb_lcbr, SE_LCBR, MT_SPC, SE_S) +COMB(comb_rcbr, SE_RCBR, MT_SPC, SE_T) +COMB(comb_mins, SE_MINS, MT_SPC, SE_H) +COMB(comb_bsls, SE_BSLS, MT_SPC, SE_K) +COMB(comb_grv, GRV, SE_E, SE_M) +COMB(comb_ques, SE_QUES, SE_E, SE_N) +COMB(comb_lbrc, SE_LBRC, SE_E, SE_A) +COMB(comb_rbrc, SE_RBRC, SE_E, SE_I) +// Lower +COMB(comb_labk, SE_LABK, MT_SPC, SE_V) +COMB(comb_rabk, SE_RABK, MT_SPC, SE_G) +COMB(comb_perc, SE_PERC, MT_SPC, SE_D) +COMB(comb_ampr, SE_AMPR, SE_E, SE_L) +COMB(comb_lprn, SE_LPRN, SE_E, SE_LPRN) +COMB(comb_rprn, SE_RPRN, SE_E, SE_RPRN) +COMB(comb_unds, SE_UNDS, SE_E, SE_UNDS) + +// Swedish letters with cross side thumb +COMB(arng, SE_ARNG, MT_SPC, SE_LPRN) +COMB(adia, SE_ADIA, MT_SPC, SE_RPRN) +COMB(odia, SE_ODIA, MT_SPC, SE_UNDS) + +// Specials +SUBS(https, "https://", MT_SPC, SE_SLSH) +COMB(ctrl_gui_space, C(G(KC_SPACE)), MT_SPC, SE_N, SE_A) diff --git a/keyboards/ferris/keymaps/treeman/config.h b/keyboards/ferris/keymaps/treeman/config.h new file mode 100644 index 0000000000..3da48c1d38 --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/config.h @@ -0,0 +1,67 @@ +/* Copyright 2019 Thomas Baart + * + * 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 . + */ + +#pragma once + +#ifdef RGBLIGHT_ENABLE +# define RGBLIGHT_DISABLE_KEYCODES +# define RGBLIGHT_LAYERS +#endif + +// Home-row mods: https://precondition.github.io/home-row-mods#tap-hold-configuration-settings +// Configure the global tapping term (default: 200ms) +#define TAPPING_TERM_PER_KEY +#ifdef TAPPING_TERM_PER_KEY +# define TAPPING_TERM 170 +#else +// Only for thumbs +# define TAPPING_TERM 220 +#endif + +// Prevent normal rollover on alphas from accidentally triggering mods. +// Now the default behavior! +// #define IGNORE_MOD_TAP_INTERRUPT +// Enable rapid switch from tap to hold, disables double tap hold auto-repeat. +#define TAPPING_FORCE_HOLD +// Apply the modifier on keys that are tapped during a short hold of a modtap +#define PERMISSIVE_HOLD +// Immediately turn on layer if key is pressed quickly +#define HOLD_ON_OTHER_KEY_PRESS + +// Combos +#undef COMBO_TERM +#define COMBO_TERM 35 +#define COMBO_MUST_TAP_PER_COMBO +#define COMBO_TERM_PER_COMBO +// All combos are specified from the base layer, saves space +#define COMBO_ONLY_FROM_LAYER 0 + +#define DEFAULT_XCASE_SEPARATOR SE_MINS +#define XCASE_DELIMITER_KEY SE_UNDS + +// Cannot import "keymap_swedish.h" as it sometimes generates extremely weird errors. +#define LEADER_ESC_KEY KC_E + +// Not supported without flto +#define NO_ACTION_MACRO +#define NO_ACTION_FUNCTION + +// Save space +// #define LAYER_STATE_8BIT +#define LAYER_STATE_16BIT +// These are a bit iffy, as I don't know what they're doing, but it "works fine" according to Discord people +// #undef LOCKING_SUPPORT_ENABLE +// #undef LOCKING_RESYNC_ENABLE diff --git a/keyboards/ferris/keymaps/treeman/inject.h b/keyboards/ferris/keymaps/treeman/inject.h new file mode 100644 index 0000000000..2460224961 --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/inject.h @@ -0,0 +1,20 @@ +#ifdef CONSOLE_ENABLE + if (pressed) { + combo_t *combo = &key_combos[combo_index]; + uint8_t idx = 0; + uint16_t combo_keycode; + while ((combo_keycode = pgm_read_word(&combo->keys[idx])) != COMBO_END) { + uprintf("0x%04X,NA,NA,%u,%u,0x%02X,0x%02X,0\n", + combo_keycode, + /* */ + /* */ + get_highest_layer(layer_state), + pressed, + get_mods(), + get_oneshot_mods() + ); + idx++; + } + } +#endif + diff --git a/keyboards/ferris/keymaps/treeman/keycodes.h b/keyboards/ferris/keymaps/treeman/keycodes.h new file mode 100644 index 0000000000..9ab124f825 --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/keycodes.h @@ -0,0 +1,108 @@ +#pragma once + +#include QMK_KEYBOARD_H + +#include "keymap_swedish.h" +#include "quantum.h" +#include "quantum/action.h" +#include "quantum/quantum_keycodes.h" +#include "oneshot.h" + +enum layers { + _BASE = 0, + _SWE, + _NUM, + _NAV, + _WNAV, + _WIN, + _SYM, + _MODS, + _SHRT, + _SPEC, + _FUN, +}; + +enum custom_keycodes { + // Direct dead keys ~`^ + TILD = QK_USER, + GRV, + CIRC, + + // Vim + SAVE_VIM, + VIM_VS, + VIM_SP, + CLOSE_WIN, + + // Multiple chars + QU, + SC, + AT_U, + + // É + E_ACUT, + + // Custom keycodes for instant processing for NUMWORD + NUM_G, + + // Custom Win + Alt for window toggling on Windows + WIN_ALT, + + // Instant oneshot mods + OS_SHFT, + OS_CTRL, + OS_ALT, + OS_GUI, + + // Smart caps lock and layers that turn off on certain keys + CAPSWORD, + NUMWORD, + + // Layer management + CANCEL, // Cancel SYMWORD and NUMWORD + CLEAR, // Clear all WORD, one-shots and reset to BASE + + TG_SWE, + + // Instant leader key + LEADER, + + // Repeat keys + REPEAT, + REV_REP, +}; + +#define xxxxxxx KC_NO + +#define MT_SPC LT(_NAV, KC_SPC) +#define MY_RALT OSM(MOD_RALT) +#define DN_CTRL LCTL_T(KC_DOWN) + +#define ESC_SYM LT(_SYM, KC_ESC) +#define TAB_MOD LT(_MODS, KC_TAB) + +#define COLN_SYM LT(_SYM, SE_COLN) + +#define C_TAB C(KC_TAB) +#define S_TAB S(KC_TAB) +#define SC_TAB S(C(KC_TAB)) + +#define MY_LALT OSM(MOD_LALT) + +#define SHRT OSL(_SHRT) +#define WNAV MO(_WNAV) +#define TG_WNAV TG(_WNAV) +#define OPT OSL(_OPT) +#define SPEC OSL(_SPEC) +#define FUN OSL(_FUN) + +#define GAME2 OSL(_GAME2) + +#define SYM_LFT ALGR(SE_Y) // ← y +#define SYM_DWN ALGR(SE_U) // ↓ u +#define SYM_RHT ALGR(SE_I) // → i +#define SYM_UP ALGR(S(SE_U)) // ↑ U +#define SYM_LDQ ALGR(SE_V) // “ v +#define SYM_RDQ ALGR(SE_B) // ” b +#define SYM_LQO ALGR(S(SE_V)) // ‘ V +#define SYM_RQO ALGR(S(SE_B)) // ’ B diff --git a/keyboards/ferris/keymaps/treeman/keymap.c b/keyboards/ferris/keymaps/treeman/keymap.c new file mode 100644 index 0000000000..469642c8e3 --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/keymap.c @@ -0,0 +1,860 @@ +/* Copyright 2019 Thomas Baart + * + * 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 . + */ +#include +#include "quantum.h" +#include QMK_KEYBOARD_H + +// "Nu\nSh\nFn\n9\nx\nz\n4\n6\n2\nJ\n8\ny", +#include "keycodes.h" +#include "status.h" +#include "oneshot.h" +#include "casemodes.h" +#include "layermodes.h" +#include "tap_hold.h" +#include "repeat.h" +#include "roll.h" +#include "leader.h" + +#include "keymap_swedish.h" +#include "sendstring_swedish.h" +#include "g/keymap_combo.h" + +#ifdef CONSOLE_ENABLE +# include "print.h" +#endif + +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + /* + * Base Layer: Modified RSTHD + */ + [_BASE] = LAYOUT(SE_J, SE_C, SE_Y, SE_F, SE_P, SE_X, SE_W, SE_O, SE_U, SE_DOT, SE_R, SE_S, SE_T, SE_H, SE_K, SE_M, SE_N, SE_A, SE_I, REPEAT, SE_COMM, SE_V, SE_G, SE_D, SE_B, SE_SLSH, SE_L, SE_LPRN, SE_RPRN, SE_UNDS, SHRT, MT_SPC, SE_E, SPEC), + [_SWE] = LAYOUT(_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, SE_ARNG, SE_ADIA, SE_ODIA, _______, _______, _______, _______), + [_NUM] = LAYOUT(SE_J, SE_PLUS, SE_ASTR, SE_EXLM, SE_P, SE_X, _______, AT_U, REPEAT, _______, SE_6, SE_4, SE_0, SE_2, SE_K, _______, SE_3, SE_1, SE_5, SE_7, SE_COMM, _______, NUM_G, SE_8, _______, SE_SLSH, SE_9, SE_LPRN, SE_RPRN, SE_UNDS, _______, _______, CANCEL, _______), + [_NAV] = LAYOUT(G(SE_J), KC_LEFT, KC_UP, KC_RGHT, KC_HOME, xxxxxxx, G(SE_W), G(SE_E), G(SE_R), xxxxxxx, KC_PGUP, SC_TAB, DN_CTRL, C_TAB, G(SE_K), xxxxxxx, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT, KC_ENT, xxxxxxx, xxxxxxx, KC_PGDN, KC_END, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, _______, _______, WNAV, _______), + [_WIN] = LAYOUT(_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, S_TAB, _______, KC_TAB, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______), + // Important that the symbols on the base layer have the same positions as these symbols + [_SYM] = LAYOUT(TILD, SE_PLUS, SE_ASTR, SE_EXLM, xxxxxxx, xxxxxxx, SE_HASH, SE_AT, CIRC, SE_DOT, SE_PIPE, SE_LCBR, SE_RCBR, SE_MINS, SE_BSLS, GRV, SE_QUES, SE_LBRC, SE_RBRC, REPEAT, SE_COMM, SE_LABK, SE_RABK, SE_PERC, xxxxxxx, SE_SLSH, SE_AMPR, SE_LPRN, SE_RPRN, SE_UNDS, _______, _______, CANCEL, _______), + [_MODS] = LAYOUT(_______, _______, _______, _______, _______, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, _______, _______, _______, _______, _______, xxxxxxx, OS_GUI, OS_CTRL, OS_SHFT, OS_ALT, _______, _______, _______, _______, _______, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, MY_RALT, _______, _______, _______, _______), + [_SHRT] = LAYOUT(C(SE_Q), C(SE_W), C(SE_E), C(SE_R), C(SE_T), _______, _______, _______, _______, _______, C(SE_A), C(SE_S), C(SE_D), C(SE_F), C(SE_G), _______, _______, _______, _______, _______, C(SE_Z), C(SE_X), C(SE_C), C(SE_V), C(SE_B), _______, _______, _______, _______, _______, _______, _______, _______, FUN), + /* + [_SHRT] = LAYOUT( + C(SE_A), C(SE_C), C(SE_W), C(SE_F), C(SE_E), _______, _______, _______, _______, _______, + C(SE_R), C(SE_S), C(SE_T), C(SE_H), xxxxxxx, _______, _______, _______, _______, _______, + C(SE_X), C(SE_V), C(SE_G), C(SE_D), C(SE_B), _______, _______, _______, _______, _______, + _______, _______, _______, FUN + ), + */ + [_WNAV] = LAYOUT(G(SE_J), G(SE_C), xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, G(SE_W), G(SE_E), G(SE_R), xxxxxxx, G(SE_6), G(SE_4), G(SE_0), G(SE_2), G(SE_K), xxxxxxx, G(SE_3), G(SE_1), G(SE_5), G(SE_7), xxxxxxx, xxxxxxx, xxxxxxx, G(SE_8), xxxxxxx, xxxxxxx, G(SE_9), G(SE_H), G(SE_L), xxxxxxx, _______, G(KC_SPC), _______, _______), + [_FUN] = LAYOUT(xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC_F6, KC_F4, KC_F10, KC_F2, KC_F12, KC_F11, KC_F3, KC_F1, KC_F5, KC_F7, xxxxxxx, xxxxxxx, xxxxxxx, KC_F8, xxxxxxx, xxxxxxx, KC_F9, xxxxxxx, xxxxxxx, xxxxxxx, _______, _______, _______, _______), + [_SPEC] = LAYOUT(SE_TILD, SYM_LQO, _______, SYM_RQO, _______, _______, _______, _______, SE_CIRC, SE_DIAE, _______, SYM_LDQ, _______, SYM_RDQ, SE_ACUT, SE_GRV, SYM_LFT, SYM_DWN, SYM_UP, SYM_RHT, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, FUN, _______, _______, _______)}; + +// Keyboard utils + +static uint16_t last_key_down = KC_NO; +static uint16_t last_key_up = KC_NO; + +static bool linux_mode = true; +bool in_linux(void) { + return linux_mode; +} + +static bool swap_caps_escape = false; +bool is_caps_swapped(void) { + return swap_caps_escape; +} + +bool tap_undead_key(bool key_down, uint16_t code) { + if (key_down) { + tap_code16(code); + tap_code16(KC_SPACE); + } + return false; +} + +void tap16_repeatable(uint16_t keycode) { + tap_code16(keycode); + register_key_to_repeat(keycode); +} + +void swap_caps_esc(void) { + swap_caps_escape = !swap_caps_escape; +#ifdef OLED_DRIVER_ENABLE + oled_on(); +#endif +} + +void tg_nix(void) { + linux_mode = !linux_mode; +#ifdef OLED_DRIVER_ENABLE + oled_on(); +#endif +} + +bool process_caps(bool key_down) { + if (swap_caps_escape) { + if (key_down) { + register_code(KC_ESC); + } else { + unregister_code(KC_ESC); + } + return false; + } + return true; +} + +bool process_escape(bool key_down) { + if (swap_caps_escape) { + if (key_down) { + register_code(KC_CAPS); + } else { + unregister_code(KC_CAPS); + } + return false; + } + return true; +} + +void tap_escape(void) { + tap_code(swap_caps_escape ? KC_CAPS : KC_ESC); +} + +void tap_caps_lock(void) { + tap_code(swap_caps_escape ? KC_ESC : KC_CAPS); +} + +void enable_gaming(void) { + /* autoshift_disable(); */ + /* layer_on(_GAME); */ +} +void disable_gaming(void) { + /* autoshift_enable(); */ + /* layer_off(_GAME); */ + /* layer_off(_GAME2); */ +} + +void tap_space_shift(uint16_t key, bool key_down) { + if (key_down) { + tap_code16(key); + tap_code(KC_SPC); + set_oneshot_mods(MOD_BIT(KC_LSFT)); + } +} + +void double_tap(uint16_t keycode) { + tap_code16(keycode); + tap_code16(keycode); +} + +void double_tap_space(uint16_t keycode) { + tap_code16(KC_SPC); + double_tap(keycode); + tap_code16(KC_SPC); +} + +uint16_t corresponding_swe_key(uint16_t keycode) { + switch (keycode) { + case SE_LPRN: + return SE_ARNG; + case SE_ARNG: + return SE_LPRN; + case SE_RPRN: + return SE_ADIA; + case SE_ADIA: + return SE_RPRN; + case SE_UNDS: + return SE_ODIA; + case SE_ODIA: + return SE_UNDS; + default: + return KC_NO; + } +} + +// Combos + +uint16_t get_combo_term(uint16_t index, combo_t *combo) { + switch (index) { + // Home-row and other tight combos + case ctrl_combo_l: + case escape_sym: + case tab_mod: + case del: + case dquo: + case coln_sym: + case ctrl_combo_r: + case quot: + case ent: + case vsp: + case gui_combo_l: + case gui_combo_r: + case dlr: + return COMBO_TERM; + // Vertical combos, very relaxed + case small_left_arrow: + case lt_eq: + case large_right_arrow: + case small_right_arrow: + case pipe_to: + case sp: + case gt_eq: + return COMBO_TERM + 55; + // Regular combos, slightly relaxed + default: + return COMBO_TERM + 25; + } +} + +bool get_combo_must_tap(uint16_t index, combo_t *combo) { + switch (index) { + case del: + case backsp: + case q_comb: + case qu_comb: + case z_comb: + case num: + case comb_perc: + case comb_grv: + case comb_hash: + case comb_pipe: + case comb_ques: + case comb_exlm: + case comb_ampr: + case comb_labk: + case comb_rabk: + case comb_lcbr: + case comb_lbrc: + case comb_at: + case comb_0: + case comb_e_acut: + case rev_rep: + case arng: + case adia: + case odia: + case eql: + case gui_combo_l: + case gui_combo_r: + case ctrl_combo_l: + case ctrl_combo_r: + case shift_combo_l: + case shift_combo_r: + case close_win: + case escape_sym: + case tab_mod: + case coln_sym: + case dquo: + case lalt: + case win_alt: + return false; + default: + return true; + } +} + +bool combo_should_trigger(uint16_t combo_index, combo_t *combo) { + // FIXME this doesn't seem to work? + return true; +} + +// Tapping terms + +#ifdef TAPPING_TERM_PER_KEY + +# define THUMB_TERM 20 +# define INDEX_TERM -20 +# define MIDDLE_TERM 0 +# define RING_TERM 80 +# define PINKY_TERM 180 + +uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + case MT_SPC: + return TAPPING_TERM + THUMB_TERM; + case DN_CTRL: + return TAPPING_TERM + MIDDLE_TERM; + default: + return TAPPING_TERM; + } +} +#endif + +// Case modes + +bool terminate_case_modes(uint16_t keycode, const keyrecord_t *record) { + switch (keycode) { + // Keycodes to ignore (don't disable caps word) + case REPEAT: + case REV_REP: + return false; + case SE_A ... SE_Z: + case SE_1 ... SE_0: + case SE_ARNG: + case SE_ADIA: + case SE_ODIA: + case QU: + case SC: + case SE_MINS: + case SE_UNDS: + case KC_BSPC: + // If mod chording disable the mods + if (record->event.pressed && (get_mods() != 0)) { + return true; + } + break; + default: + if (record->event.pressed) { + return true; + } + break; + } + return false; +} + +void triple_tap(uint16_t keycode) { + tap_code16(keycode); + tap_code16(keycode); + tap_code16(keycode); +} + +void double_parens_left(uint16_t left, uint16_t right) { + tap_code16(left); + tap_code16(right); + tap_code16(KC_LEFT); +} + +// One-shot mods + +bool is_oneshot_cancel_key(uint16_t keycode) { + switch (keycode) { + case CLEAR: + return true; + default: + return false; + } +} + +bool is_oneshot_ignored_key(uint16_t keycode) { + switch (keycode) { + case CLEAR: + case OS_SHFT: + case OS_CTRL: + case OS_ALT: + case OS_GUI: + case TAB_MOD: + return true; + default: + return false; + } +} + +oneshot_state os_shft_state = os_up_unqueued; +oneshot_state os_ctrl_state = os_up_unqueued; +oneshot_state os_alt_state = os_up_unqueued; +oneshot_state os_gui_state = os_up_unqueued; + +void process_oneshot_pre(uint16_t keycode, keyrecord_t *record) { + update_oneshot_pre(&os_shft_state, KC_LSFT, OS_SHFT, keycode, record); + update_oneshot_pre(&os_ctrl_state, KC_LCTL, OS_CTRL, keycode, record); + update_oneshot_pre(&os_alt_state, KC_LALT, OS_ALT, keycode, record); + update_oneshot_pre(&os_gui_state, KC_LGUI, OS_GUI, keycode, record); +} + +void process_oneshot_post(uint16_t keycode, keyrecord_t *record) { + update_oneshot_post(&os_shft_state, KC_LSFT, OS_SHFT, keycode, record); + update_oneshot_post(&os_ctrl_state, KC_LCTL, OS_CTRL, keycode, record); + update_oneshot_post(&os_alt_state, KC_LALT, OS_ALT, keycode, record); + update_oneshot_post(&os_gui_state, KC_LGUI, OS_GUI, keycode, record); +} + +void process_oneshot_key(uint16_t keycode, keyrecord_t *record) { + update_oneshot_pre(&os_shft_state, KC_LSFT, OS_SHFT, keycode, record); + update_oneshot_post(&os_ctrl_state, KC_LCTL, OS_CTRL, keycode, record); +} + +// Tap hold + +bool tap_hold(uint16_t keycode) { + switch (keycode) { + case SE_DQUO: + case SE_LABK: + case SE_RABK: + case SE_DOT: + case SE_PERC: + case GRV: + case SE_AT: + case SE_PIPE: + case SE_EXLM: + case SE_AMPR: + case SE_QUES: + case SE_HASH: + case SE_LPRN: + case SE_LCBR: + case SE_LBRC: + case SE_EQL: + case SE_UNDS: + case SE_0: + case G(SE_0): + case G(SE_1): + case G(SE_2): + case G(SE_3): + case G(SE_4): + case G(SE_5): + case G(SE_6): + case G(SE_7): + case G(SE_8): + case G(SE_9): + case G(SE_K): + case G(SE_J): + case G(SE_W): + case G(SE_E): + case G(SE_R): + case G(SE_C): + case SE_A ... SE_Z: + case SE_ARNG: + case SE_ADIA: + case SE_ODIA: + case QU: + case SC: + case E_ACUT: + case CLOSE_WIN: + case C(SE_A): + case C(SE_C): + case C(SE_W): + case C(SE_F): + case C(SE_E): + case C(SE_R): + case C(SE_S): + case C(SE_T): + case C(SE_H): + case C(SE_X): + case C(SE_V): + case C(SE_G): + case C(SE_D): + case C(SE_B): + return true; + default: + return false; + } +} + +void tap_hold_send_tap(uint16_t keycode) { + switch (keycode) { + case GRV: + register_key_to_repeat(keycode); + tap_undead_key(true, SE_GRV); + return; + case QU: + send_string("qu"); + return; + case SC: + send_string("sc"); + return; + case SE_Q: + case SE_Z: + if (IS_LAYER_ON(_SHRT) || last_key_up == SHRT) { + tap16_repeatable(C(keycode)); + } else { + tap16_repeatable(keycode); + } + return; + case E_ACUT: + tap_code16(SE_ACUT); + tap_code16(SE_E); + return; + case CLOSE_WIN: + tap_code16(C(SE_W)); + tap_code(SE_Q); + /* tap_escape(); */ + /* tap_code16(SE_COLN); */ + /* tap_code(SE_Q); */ + /* tap_code(KC_ENT); */ + return; + default: + tap16_repeatable(keycode); + } +} + +void tap_hold_send_hold(uint16_t keycode) { + disable_caps_word(); + + switch (keycode) { + case SE_LABK: + case SE_RABK: + case SE_UNDS: + // FIXME should be repeatable + double_tap(keycode); + return; + case SE_DQUO: + case SE_DOT: + case SE_0: + triple_tap(keycode); + return; + case SE_PERC: + send_string("%{}"); + return; + case GRV: + tap_undead_key(true, SE_GRV); + tap_undead_key(true, SE_GRV); + tap_undead_key(true, SE_GRV); + return; + case SE_AT: + tap_code16(SE_AT); + tap16_repeatable(SE_U); + return; + case SE_PIPE: + case SE_AMPR: + case SE_EQL: + double_tap_space(keycode); + return; + case SE_EXLM: + send_string(" != "); + return; + case SE_QUES: + send_string("{:?}"); + return; + case SE_HASH: + send_string("{:#?}"); + return; + case SE_LPRN: + double_parens_left(keycode, SE_RPRN); + return; + case SE_LCBR: + double_parens_left(keycode, SE_RCBR); + return; + case SE_LBRC: + double_parens_left(keycode, SE_RBRC); + return; + case QU: + send_string("Qu"); + return; + case SC: + send_string("Sc"); + return; + case E_ACUT: + tap_code16(SE_ACUT); + tap_code16(S(SE_E)); + return; + case CLOSE_WIN: + tap16_repeatable(S(G(SE_C))); + return; + case SE_Q: + case SE_Z: + if (IS_LAYER_ON(_SHRT) || last_key_up == SHRT) { + tap16_repeatable(S(C(keycode))); + } else { + tap16_repeatable(S(keycode)); + } + return; + default: + tap16_repeatable(S(keycode)); + } +} + +uint16_t tap_hold_timeout(uint16_t keycode) { + switch (keycode) { + // Extra + case CLOSE_WIN: + return 160; + // Thumb + case SE_E: + return 120; + // Pinky + case SE_R: + case SE_COMM: + case SE_UNDS: + // case UNDS_ODIA: + case SE_6: + case G(SE_6): + case SE_7: + case G(SE_7): + case C(SE_R): + case C(SE_X): + return 135; + // Ring + case SE_J: + case SE_C: + case SE_S: + case SE_V: + case SE_U: + case SE_DOT: + case SE_I: + case SE_RPRN: + // case RPRN_ADIA: + case SE_Q: + case QU: + case SE_4: + case G(SE_4): + case SE_5: + case G(SE_5): + case G(SE_J): + case G(SE_R): + case C(SE_A): + case C(SE_C): + case C(SE_S): + case C(SE_V): + return 105; + // Middle + case SE_Y: + case SE_T: + case SE_G: + case SE_O: + case SE_A: + case SE_LPRN: + // case LPRN_ARNG: + case SE_Z: + case SE_0: + case G(SE_0): + case SE_1: + case G(SE_1): + case C(SE_W): + case C(SE_T): + case C(SE_G): + return 100; + // Slow index + case SE_P: + case SE_X: + case C(SE_E): + return 105; + // Index + default: + return 100; + } +} + +// https://github.com/andrewjrae/kyria-keymap#userspace-leader-sequences +void *leader_toggles_func(uint16_t keycode) { + switch (keycode) { + case KC_N: + layer_invert(_NUM); + return NULL; + case KC_S: + layer_invert(_SYM); + return NULL; + case KC_C: + swap_caps_esc(); + return NULL; + default: + return NULL; + } +} + +void *leader_start_func(uint16_t keycode) { + switch (keycode) { + case KC_T: + return leader_toggles_func; + case KC_C: + tap_caps_lock(); + return NULL; + case ESC_SYM: + tap_code16(C(S(KC_ESC))); + return NULL; + default: + return NULL; + } +} + +bool _process_record_user(uint16_t keycode, keyrecord_t *record) { + // Error: Too many arguments for format. Meh! + // #ifdef CONSOLE_ENABLE + // if (record->event.pressed) { + // uprintf("0x%04X,%u,%u,%u,%b,0x%02X,0x%02X,%u\n", + // keycode, + // record->event.key.row, + // record->event.key.col, + // get_highest_layer(layer_state), + // record->event.pressed, + // get_mods(), + // get_oneshot_mods(), + // record->tap.count + // ); + // } + // #endif + + if (!process_leader(keycode, record)) { + return false; + } + if (!process_num_word(keycode, record)) { + return false; + } + if (!process_case_modes(keycode, record)) { + return false; + } + if (!process_roll(keycode, record)) { + return false; + } + if (!process_tap_hold(keycode, record)) { + // Extra register here to allow fast rolls without waiting for tap hold, + // (which will also overwrite this). + if (record->event.pressed) { + register_key_to_repeat(keycode); + } + return false; + } + + switch (keycode) { + case ESC_SYM: + if (record->tap.count && record->event.pressed) { + tap_escape(); + return false; + } + break; + case KC_CAPS: + return process_caps(record->event.pressed); + case CLEAR: + clear_oneshot_mods(); + if (get_oneshot_layer() != 0) { + clear_oneshot_layer_state(ONESHOT_OTHER_KEY_PRESSED); + } + stop_leading(); + layer_off(_NUM); + layer_off(_SYM); + layer_move(_BASE); + return false; + case CANCEL: + layer_off(_NUM); + layer_off(_SYM); + stop_leading(); + // disable_gaming(); + return false; + case TILD: + register_key_to_repeat(TILD); + return tap_undead_key(record->event.pressed, SE_TILD); + case CIRC: + register_key_to_repeat(CIRC); + return tap_undead_key(record->event.pressed, SE_CIRC); + case NUMWORD: + process_num_word_activation(record); + return false; + case CAPSWORD: + if (record->event.pressed) { + enable_caps_word(); + } + return false; + case SAVE_VIM: + if (record->event.pressed) { + tap_escape(); + tap_code16(SE_COLN); + tap_code(SE_W); + tap_code(KC_ENT); + } + return false; + case VIM_SP: + if (record->event.pressed) { + tap_code16(C(SE_W)); + tap_code(SE_S); + } + return false; + case VIM_VS: + if (record->event.pressed) { + tap_code16(C(SE_W)); + tap_code(SE_V); + } + return false; + case NUM_G: + if (record->event.pressed) { + tap_code16(S(SE_G)); + } + return false; + case AT_U: + if (record->event.pressed) { + tap_code16(SE_AT); + tap16_repeatable(SE_U); + } + return false; + case COLN_SYM: + if (record->tap.count && record->event.pressed) { + tap16_repeatable(SE_COLN); + return false; + } + break; + case KC_ENT: + if (record->event.pressed) { + if (IS_LAYER_ON(_NUM)) { + tap16_repeatable(KC_PENT); + } else { + tap16_repeatable(KC_ENT); + } + disable_num_word(); + } + return false; + case TG_SWE: + if (record->event.pressed) { + uint16_t swe_key = corresponding_swe_key(last_key()); + if (swe_key != KC_NO) { + tap_code16(KC_BSPC); + tap_code16(swe_key); + } + layer_invert(_SWE); + } + return false; + case WIN_ALT: + // Always start by sending Alt Tab to goto the next window with only a combo tap. + // We can then do Tab/S-Tab to continue moving around the windows if we want to. + if (record->event.pressed) { + register_code(KC_LALT); + tap_code16(KC_TAB); + layer_on(_WIN); + } else { + layer_off(_WIN); + unregister_code(KC_LALT); + } + return false; + case LEADER: + start_leading(); + return false; + case REPEAT: + // Enable fast UI rolls with repeat key + end_tap_hold(); + update_repeat_key(record); + return false; + case REV_REP: + update_reverse_repeat_key(record); + return false; + } + + return true; +} + +bool process_record_user(uint16_t keycode, keyrecord_t *record) { + process_oneshot_pre(keycode, record); + + // If `false` was returned, then we did something special and should register that manually. + // Otherwise register keyrepeat here by default. + bool res = _process_record_user(keycode, record); + + // Space needs some special handling to not interfere with NAV toggling. + // Maybe there's a more general way to do this, but I dunno. + if (keycode == MT_SPC) { + if (!record->event.pressed && last_key_down == MT_SPC) { + register_key_to_repeat(KC_SPC); + } + } else if (res && record->event.pressed) { + register_key_to_repeat(keycode); + } + + process_oneshot_post(keycode, record); + + if (record->event.pressed) { + last_key_down = keycode; + } else { + last_key_up = keycode; + } + + return res; +} + +void matrix_scan_user(void) { + tap_hold_matrix_scan(); +} diff --git a/keyboards/ferris/keymaps/treeman/layermodes.c b/keyboards/ferris/keymaps/treeman/layermodes.c new file mode 100644 index 0000000000..8de8d850b1 --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/layermodes.c @@ -0,0 +1,74 @@ +#include "layermodes.h" +#include "keycodes.h" + +static uint16_t num_word_timer; +static bool _num_word_enabled = false; +bool num_word_enabled(void) { + return _num_word_enabled; +} +void enable_num_word(void) { + _num_word_enabled = true; + layer_on(_NUM); +} +void disable_num_word(void) { + _num_word_enabled = false; + layer_off(_NUM); +} +void process_num_word_activation(const keyrecord_t *record) { + if (record->event.pressed) { + layer_on(_NUM); + num_word_timer = timer_read(); + } else { + if (timer_elapsed(num_word_timer) < TAPPING_TERM) { + // Tapping enables NUMWORD + _num_word_enabled = true; + } else { + // Holding turns off NUM when released + layer_off(_NUM); + } + } +} +bool process_num_word(uint16_t keycode, const keyrecord_t *record) { + if (!_num_word_enabled) return true; + + switch (keycode) { + case QK_MOD_TAP ... QK_MOD_TAP_MAX: + case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: + case QK_TAP_DANCE ... QK_TAP_DANCE_MAX: + if (record->tap.count == 0) + return true; + keycode = keycode & 0xFF; + } + switch (keycode) { + case SE_1 ... SE_0: + case SE_PERC: + case SE_COMM: + case SE_DOT: + case SE_SLSH: + case SE_MINS: + case SE_ASTR: + case SE_PLUS: + case SE_COLN: + case SE_EQL: + case SE_UNDS: + case KC_BSPC: + case KC_X: + case REPEAT: + case REV_REP: + case KC_ENT: + case xxxxxxx: + // Don't disable for above keycodes + break; + case CANCEL: + if (record->event.pressed) { + disable_num_word(); + } + return false; + default: + if (record->event.pressed) { + disable_num_word(); + } + } + return true; +} + diff --git a/keyboards/ferris/keymaps/treeman/layermodes.h b/keyboards/ferris/keymaps/treeman/layermodes.h new file mode 100644 index 0000000000..73a71576af --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/layermodes.h @@ -0,0 +1,15 @@ +#pragma once + +#include QMK_KEYBOARD_H + +// Check whether numword is on +bool num_word_enabled(void); +// Enable numword +void enable_num_word(void); +// Disable numword +void disable_num_word(void); +// Process numword activation, to be placed in process user as a keycode +void process_num_word_activation(const keyrecord_t *record); +// Process numword, to be placed in process user +bool process_num_word(uint16_t keycode, const keyrecord_t *record); + diff --git a/keyboards/ferris/keymaps/treeman/leader.c b/keyboards/ferris/keymaps/treeman/leader.c new file mode 100644 index 0000000000..2db54086e8 --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/leader.c @@ -0,0 +1,135 @@ +/* Copyright 2021 Andrew Rae ajrae.nv@gmail.com @andrewjrae + * + * 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 . + */ + +#include "leader.h" + +#include + +#ifndef LEADER_ESC_KEY +# define LEADER_ESC_KEY KC_ESC +#endif + +static bool leading = false; +static leader_func_t leader_func = NULL; + +#ifdef LEADER_DISPLAY_STR + +# ifndef LEADER_DISPLAY_LEN +# define LEADER_DISPLAY_LEN 19 +# endif + +static const char keycode_to_ascii_lut[58] = {0, 0, 0, 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 0, 0, 0, '\t', ' ', '-', '=', '[', ']', '\\', 0, ';', '\'', '`', ',', '.', '/'}; + +static uint8_t leader_display_size; +static const char space_ascii[] = "SPC"; +static char leader_display[LEADER_DISPLAY_LEN + 1]; // add space for null terminator + +static void update_leader_display(uint16_t keycode) { + leader_display[leader_display_size] = ' '; + ++leader_display_size; + if (leader_display_size < LEADER_DISPLAY_LEN) { + switch (keycode) { + case KC_SPC: + memcpy(leader_display + leader_display_size, space_ascii, sizeof(space_ascii)); + leader_display_size += sizeof(space_ascii); + break; + default: + if (keycode < sizeof(keycode_to_ascii_lut)) { + leader_display[leader_display_size] = keycode_to_ascii_lut[keycode]; + } else { + leader_display[leader_display_size] = '?'; + } + ++leader_display_size; + break; + } + leader_display[leader_display_size] = '-'; + } +} + +char *leader_display_str(void) { + return leader_display; +} +#endif + +// The entry point for leader sequenc functions +__attribute__((weak)) void *leader_start_func(uint16_t keycode) { + return NULL; +} + +// Check to see if we are leading +bool is_leading(void) { + return leading; +} +// Start leader sequence +void start_leading(void) { + leading = true; + leader_func = leader_start_func; +#ifdef LEADER_DISPLAY_STR + memset(leader_display, 0, sizeof(leader_display)); + leader_display[0] = 'L'; + leader_display[1] = 'D'; + leader_display[2] = 'R'; + leader_display[3] = '-'; + leader_display_size = 3; +#endif +} +// Stop leader sequence +void stop_leading(void) { + leading = false; + leader_func = NULL; +#ifdef LEADER_DISPLAY_STR + leader_display[leader_display_size] = ' '; +#endif +} + +// Process keycode for leader sequences +bool process_leader(uint16_t keycode, const keyrecord_t *record) { + if (leading && record->event.pressed) { + // Get the base keycode of a mod or layer tap key + switch (keycode) { + case QK_MOD_TAP ... QK_MOD_TAP_MAX: + case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: + case QK_TAP_DANCE ... QK_TAP_DANCE_MAX: + // Earlier return if this has not been considered tapped yet + if (record->tap.count == 0) return true; + keycode = keycode & 0xFF; + break; + default: + break; + } + + // let through anything above that's normal keyboard keycode or a mod + if (keycode > QK_MODS_MAX || IS_MODIFIER_KEYCODE(keycode)) { + return true; + } + // early exit if the esc key was hit + if (keycode == LEADER_ESC_KEY) { + stop_leading(); + return false; + } + +#ifdef LEADER_DISPLAY_STR + update_leader_display(keycode); +#endif + // update the leader function + leader_func = leader_func(keycode); + if (leader_func == NULL) { + stop_leading(); + } + return false; + } + return true; +} diff --git a/keyboards/ferris/keymaps/treeman/leader.h b/keyboards/ferris/keymaps/treeman/leader.h new file mode 100644 index 0000000000..0995adb142 --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/leader.h @@ -0,0 +1,51 @@ +/* Copyright 2021 Andrew Rae ajrae.nv@gmail.com @andrewjrae + * + * 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 . + */ + +#pragma once + +#include QMK_KEYBOARD_H + +typedef void *(*leader_func_t)(uint16_t); + +// Check to see if we are leading +bool is_leading(void); +// Start leader sequence +void start_leading(void); +// Stop leader sequence +void stop_leading(void); + +// Process keycode for leader sequences +bool process_leader(uint16_t keycode, const keyrecord_t *record); + +#ifdef LEADER_DISPLAY_STR +char *leader_display_str(void); + +#define OLED_LEADER_DISPLAY() { \ + static uint16_t timer = 0; \ + if (is_leading()) { \ + oled_write_ln(leader_display_str(), false); \ + timer = timer_read(); \ + } \ + else if (timer_elapsed(timer) < 175){ \ + oled_write_ln(leader_display_str(), false); \ + } \ + else { \ + /* prevent it from ever looping around */ \ + timer = timer_read() - 200; \ + oled_write_ln("", false); \ + } \ +} +#endif diff --git a/keyboards/ferris/keymaps/treeman/oneshot.c b/keyboards/ferris/keymaps/treeman/oneshot.c new file mode 100644 index 0000000000..9007ee4e7b --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/oneshot.c @@ -0,0 +1,70 @@ +// From https://github.com/callum-oakley/qmk_firmware/tree/master/users/callum + +#include "oneshot.h" + +void update_oneshot_pre( + oneshot_state *state, + uint16_t mod, + uint16_t trigger, + uint16_t keycode, + keyrecord_t *record +) { + if (keycode == trigger) { + if (record->event.pressed) { + // Trigger keydown + if (*state == os_up_unqueued) { + register_code(mod); + } + *state = os_down_unused; + } else { + // Trigger keyup + switch (*state) { + case os_down_unused: + // If we didn't use the mod while trigger was held, queue it. + *state = os_up_queued; + break; + case os_down_used: + // If we did use the mod while trigger was held, unregister it. + *state = os_up_unqueued; + unregister_code(mod); + break; + default: + break; + } + } + } else { + if (record->event.pressed) { + if (is_oneshot_cancel_key(keycode) && *state != os_up_unqueued) { + // Cancel oneshot on designated cancel keydown. + *state = os_up_unqueued; + unregister_code(mod); + } + } + } +} + +void update_oneshot_post( + oneshot_state *state, + uint16_t mod, + uint16_t trigger, + uint16_t keycode, + keyrecord_t *record +) { + if (keycode != trigger && !record->event.pressed) { + if (!is_oneshot_ignored_key(keycode)) { + // On non-ignored keyup, consider the oneshot used. + switch (*state) { + case os_down_unused: + *state = os_down_used; + break; + case os_up_queued: + *state = os_up_unqueued; + unregister_code(mod); + break; + default: + break; + } + } + } +} + diff --git a/keyboards/ferris/keymaps/treeman/oneshot.h b/keyboards/ferris/keymaps/treeman/oneshot.h new file mode 100644 index 0000000000..c56100867c --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/oneshot.h @@ -0,0 +1,39 @@ +// From https://github.com/callum-oakley/qmk_firmware/tree/master/users/callum +#pragma once + +#include QMK_KEYBOARD_H + +// Represents the four states a oneshot key can be in +typedef enum { + os_up_unqueued, + os_up_queued, + os_down_unused, + os_down_used, +} oneshot_state; + +// Custom oneshot mod implementation that doesn't rely on timers. If a mod is +// used while it is held it will be unregistered on keyup as normal, otherwise +// it will be queued and only released after the next non-mod keyup. +void update_oneshot_pre( + oneshot_state *state, + uint16_t mod, + uint16_t trigger, + uint16_t keycode, + keyrecord_t *record +); +void update_oneshot_post( + oneshot_state *state, + uint16_t mod, + uint16_t trigger, + uint16_t keycode, + keyrecord_t *record +); + +// To be implemented by the consumer. Defines keys to cancel oneshot mods. +bool is_oneshot_cancel_key(uint16_t keycode); + +// To be implemented by the consumer. Defines keys to ignore when determining +// whether a oneshot mod has been used. Setting this to modifiers and layer +// change keys allows stacking multiple oneshot modifiers, and carrying them +// between layers. +bool is_oneshot_ignored_key(uint16_t keycode); diff --git a/keyboards/ferris/keymaps/treeman/repeat.c b/keyboards/ferris/keymaps/treeman/repeat.c new file mode 100644 index 0000000000..2dd6cf44c5 --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/repeat.c @@ -0,0 +1,82 @@ +#include "repeat.h" +#include "keymap_swedish.h" +#include "keycodes.h" + +uint16_t last_keycode = KC_NO; + +bool tap_undead_key(bool key_down, uint16_t code); + +uint16_t last_key(void) { + return last_keycode; +} + +void register_key_to_repeat(uint16_t keycode) { + // Get the base keycode of a mod or layer tap key + switch (keycode) { + case QK_MOD_TAP ... QK_MOD_TAP_MAX: + case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: + keycode = keycode & 0xFF; + break; + } + + // Merge current mod state with keycode, for easy comparison when + // we want to do special key reverse repeats. + uint8_t mods = get_mods() | get_oneshot_mods(); + if (mods & MOD_MASK_CTRL) keycode |= QK_LCTL; + if (mods & MOD_MASK_SHIFT) keycode |= QK_LSFT; + if (mods & MOD_MASK_GUI) keycode |= QK_LGUI; + if (mods & MOD_BIT(KC_LALT)) keycode |= QK_LALT; + if (mods & MOD_BIT(KC_RALT)) keycode |= QK_RALT; + + last_keycode = keycode; +} + +void update_key(uint16_t keycode, keyrecord_t *record) { + if (record->event.pressed) { + register_code16(keycode); + } else { + unregister_code16(keycode); + } +} + +void update_repeat_key(keyrecord_t *record) { + switch (last_keycode) { + case GRV: + tap_undead_key(record->event.pressed, SE_GRV); + break; + case TILD: + tap_undead_key(record->event.pressed, SE_TILD); + break; + case CIRC: + tap_undead_key(record->event.pressed, SE_CIRC); + break; + default: + update_key(last_keycode, record); + } +} + +void update_reverse_key_pairs(uint16_t a, uint16_t b, keyrecord_t *record) { + if (last_keycode == a) { + update_key(b, record); + } else if (last_keycode == b) { + update_key(a, record); + } +} + +void update_reverse_repeat_key(keyrecord_t *record) { + // Do the "reverse" of the last pressed key, that we use to repeat + update_reverse_key_pairs(C(KC_TAB), C(S(KC_TAB)), record); + update_reverse_key_pairs(C(KC_N), C(KC_P), record); + update_reverse_key_pairs(C(KC_F), C(KC_B), record); + update_reverse_key_pairs(C(KC_U), C(KC_D), record); + update_reverse_key_pairs(C(SE_G), C(S(SE_G)), record); + update_reverse_key_pairs(KC_PGUP, KC_PGDN, record); + update_reverse_key_pairs(SE_ASTR, SE_HASH, record); + update_reverse_key_pairs(SE_LCBR, SE_RCBR, record); + update_reverse_key_pairs(G(SE_K), G(SE_J), record); + update_reverse_key_pairs(C(SE_O), C(SE_I), record); + + update_reverse_key_pairs(S(KC_W), S(KC_B), record); + update_reverse_key_pairs(SE_U, C(SE_R), record); +} + diff --git a/keyboards/ferris/keymaps/treeman/repeat.h b/keyboards/ferris/keymaps/treeman/repeat.h new file mode 100644 index 0000000000..96c2c96476 --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/repeat.h @@ -0,0 +1,11 @@ +#pragma once + +#include QMK_KEYBOARD_H + +// Register a keycode we should repeat with the repeat key +void register_key_to_repeat(uint16_t keycode); +// Last keycode +uint16_t last_key(void); +// Repeat key actions +void update_repeat_key(keyrecord_t *record); +void update_reverse_repeat_key(keyrecord_t *record); diff --git a/keyboards/ferris/keymaps/treeman/roll.c b/keyboards/ferris/keymaps/treeman/roll.c new file mode 100644 index 0000000000..1c07b647f9 --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/roll.c @@ -0,0 +1,60 @@ +#include "roll.h" +#include +#include "timer.h" + +#include "keycodes.h" + +static uint16_t overridden_key = KC_NO; +static uint16_t resulting_key = KC_NO; +static uint16_t timer = 0; +static uint16_t lastkey = KC_NO; + +void end_roll(void) { + overridden_key = KC_NO; + resulting_key = KC_NO; +} + +bool process_roll(uint16_t keycode, const keyrecord_t *record) { + if (record->event.pressed) { + uint16_t override = roll_override(lastkey, keycode); + if (overridden_key == KC_NO // Can only start a new override when the last one has finished. + && override != KC_NO && override != keycode + && timer_elapsed(timer) < ROLL_OVERRIDE_TERM) { + // Found a key we should override as a matching pair was detected within the time treshold. + overridden_key = keycode; + resulting_key = override; + } else { + // Restart our roll check. + end_roll(); + timer = timer_read(); + lastkey = keycode; + } + } + + if (overridden_key == keycode) { + // Override the key + if (record->event.pressed) { + register_code16(resulting_key); + } else { + unregister_code16(resulting_key); + // Now we can start a new override. + end_roll(); + } + return false; + } + + return true; +} + +__attribute__ ((weak)) +uint16_t roll_override(uint16_t lastkey, uint16_t keycode) { + // Add overrides like this: + /* if (lastkey == REPEAT && keycode == SE_U) { */ + /* return SE_A; */ + /* } */ + /* if (lastkey == SE_U && keycode == REPEAT) { */ + /* return SE_I; */ + /* } */ + + return KC_NO; +} diff --git a/keyboards/ferris/keymaps/treeman/roll.h b/keyboards/ferris/keymaps/treeman/roll.h new file mode 100644 index 0000000000..2745afc99c --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/roll.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include QMK_KEYBOARD_H + +// Rolls can be overridden if they're faster than this. +#define ROLL_OVERRIDE_TERM 280 + +// Process roll, place in process_record_user(). +// If this returns false, we should also return false from process_record_user(). +bool process_roll(uint16_t keycode, const keyrecord_t *record); + +// User can override this to override the currrent key to something else. +uint16_t roll_override(uint16_t lastkey, uint16_t keycode); diff --git a/keyboards/ferris/keymaps/treeman/rules.mk b/keyboards/ferris/keymaps/treeman/rules.mk new file mode 100644 index 0000000000..c22bcafb8a --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/rules.mk @@ -0,0 +1,34 @@ +COMBO_ENABLE = yes +RGBLIGHT_ENABLE = no # Keyboard RGB underglow + +# Easy definition of combos +VPATH += keyboards/gboards/ + +# Keylogging +CONSOLE_ENABLE = yes + +# Extra features that are nice but takes space +WPM_ENABLE = no +EXTRAKEY_ENABLE = no # For volume keys and similar +MOUSEKEY_ENABLE = no # Them mouse keys yo +KEY_OVERRIDE_ENABLE = no +LEADER_ENABLE = no +TAP_DANCE_ENABLE = no +# RGB_MATRIX_ENABLE = no # Per key RGB, Ferris Bling requires this + +# Saves a bunch of memory +EXTRAFLAGS += -flto +VERBOSE = no +DEBUG_MATRIX_SCAN_RATE = no +DEBUG_MATRIX = no +MAGIC_ENABLE = no +SPACE_CADET_ENABLE = no +GRAVE_ESC_ENABLE = no + +SRC += oneshot.c +SRC += layermodes.c +SRC += casemodes.c +SRC += tap_hold.c +SRC += repeat.c +SRC += roll.c +SRC += leader.c diff --git a/keyboards/ferris/keymaps/treeman/status.h b/keyboards/ferris/keymaps/treeman/status.h new file mode 100644 index 0000000000..d6c4dd198a --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/status.h @@ -0,0 +1,7 @@ +#pragma once + +#include QMK_KEYBOARD_H + +bool is_caps_swapped(void); +bool in_linux(void); + diff --git a/keyboards/ferris/keymaps/treeman/tap_hold.c b/keyboards/ferris/keymaps/treeman/tap_hold.c new file mode 100644 index 0000000000..6d21184a11 --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/tap_hold.c @@ -0,0 +1,59 @@ +#include "tap_hold.h" + +static bool in_progress = false; +static uint16_t timer = 0; +static uint16_t lastkey = KC_NO; +static uint16_t hold_timeout = 0; + +bool process_tap_hold(uint16_t keycode, const keyrecord_t *record) { + // Only process tap hold for specified keys + if (!tap_hold(keycode)) return true; + + if (record->event.pressed) { + end_tap_hold(); + in_progress = true; + timer = timer_read(); + lastkey = keycode; + hold_timeout = tap_hold_timeout(keycode); + } else { + if (in_progress && keycode == lastkey && timer_elapsed(timer) < hold_timeout) { + end_tap_hold(); + } + } + + return false; +} + +void tap_hold_matrix_scan() { + if (in_progress && timer_elapsed(timer) >= hold_timeout) { + in_progress = false; + tap_hold_send_hold(lastkey); + } +} + +void end_tap_hold() { + if (in_progress) { + in_progress = false; + tap_hold_send_tap(lastkey); + } +} + +__attribute__ ((weak)) +void tap_hold_send_tap(uint16_t keycode) { + tap_code16(keycode); +} + +__attribute__ ((weak)) +void tap_hold_send_hold(uint16_t keycode) { + tap_code16(S(keycode)); +} + +__attribute__ ((weak)) +uint16_t tap_hold_timeout(uint16_t keycode) { + return 135; +} + +__attribute__ ((weak)) +bool tap_hold(uint16_t keycode) { + return false; +} diff --git a/keyboards/ferris/keymaps/treeman/tap_hold.h b/keyboards/ferris/keymaps/treeman/tap_hold.h new file mode 100644 index 0000000000..b264ad993a --- /dev/null +++ b/keyboards/ferris/keymaps/treeman/tap_hold.h @@ -0,0 +1,20 @@ +#pragma once + +#include QMK_KEYBOARD_H + +// Process tap hold, place in process_record_user(). +// If this returns false, we should also return false from process_record_user(). +bool process_tap_hold(uint16_t keycode, const keyrecord_t *record); +// Matrix scan, place in matrix_scan_user() +void tap_hold_matrix_scan(void); +// End tap hold +void end_tap_hold(void); + +// User can override this to do whatever tap action they want. +void tap_hold_send_tap(uint16_t keycode); +// User must define this to do whatever hold action they want. +void tap_hold_send_hold(uint16_t keycode); +// Per key tap hold timeout +uint16_t tap_hold_timeout(uint16_t keycode); +// User override for what keys to trigger a tap hold action. +bool tap_hold(uint16_t keycode); diff --git a/users/muppetjones/features/casemodes.c b/users/muppetjones/features/casemodes.c index da7c5e8fa9..6257b762e6 100644 --- a/users/muppetjones/features/casemodes.c +++ b/users/muppetjones/features/casemodes.c @@ -15,6 +15,7 @@ */ #include "casemodes.h" +#include "quantum/keycodes.h" /* The caps word concept started with me @iaap on splitkb.com discord. * However it has been implemented and extended by many splitkb.com users: