Compare commits

...

10 commits

Author SHA1 Message Date
2018975b9c
try lower timeouts 2024-11-01 17:49:20 +01:00
e4f0f4bd2a
try again https://github.com/filterpaper/qmk_userspace 2024-11-01 17:33:59 +01:00
7372ce1a8b
add ferris keymap back 2024-10-26 22:50:41 +02:00
f23e38366f
layout with 34 keys 2024-10-01 23:33:02 +02:00
a958412f5a
cleanups 2024-10-01 19:16:25 +02:00
580557dc45
I can not count, it uses 34 keys 2024-10-01 19:09:22 +02:00
421578ddf6
back to 32 keys 2024-10-01 18:14:17 +02:00
194f8ff185
try night
night (CN) (8 likes)
    b f l k q  p g o u .
    n s h t m  y c a e i
    x v j d z  ' w ; / ,
          r

  SHAI:
    Alt: 34.36%
    Rol: 42.94%   (In/Out: 22.30% | 20.64%)
    One:  1.97%   (In/Out:  0.58% |  1.38%)
    Rtl: 44.90%   (In/Out: 22.88% | 22.02%)
    Red:  3.01%   (Bad:     0.09%)

    SFB: 0.58%
    SFS: 4.42%    (Red/Alt: 0.78% | 3.64%)

    LH/RH: 49.16% | 50.84%
2024-09-27 14:53:29 +02:00
b151f87eb7
more tricks from https://github.com/filterpaper/qmk_userspace 2024-09-23 01:57:34 +02:00
955857eba9
better boot and reboot keys 2024-09-23 01:43:28 +02:00
12 changed files with 194 additions and 678 deletions

View file

@ -1,22 +1,24 @@
# Christoph Cullmann's Layout
Layout with 42 keys based on Miryoku, using Dhorf as base layer:
Layout with 34 keys based on Miryoku, using Night as base layer:
dhorf (Oxey)
v l h k q j f o u ,
s r n t w y c a e i
z x m d b p g ' ; .
night (CN) (8 likes)
b f l k q p g o u .
n s h t m y c a e i
x v j d z ' w ; / ,
r
SHAI:
Alt: 31.42%
Rol: 40.03% (In/Out: 19.86% | 20.17%)
One: 1.36% (In/Out: 0.18% | 1.19%)
Red: 3.33% (Bad: 0.11%)
Alt: 34.36%
Rol: 42.94% (In/Out: 22.30% | 20.64%)
One: 1.97% (In/Out: 0.58% | 1.38%)
Rtl: 44.90% (In/Out: 22.88% | 22.02%)
Red: 3.01% (Bad: 0.09%)
SFB: 0.70%
SFS: 5.96% (Red/Alt: 0.98% | 4.98%)
SFB: 0.58%
SFS: 4.42% (Red/Alt: 0.78% | 3.64%)
LH/RH: 48.52% | 51.48%
LH/RH: 49.16% | 50.84%
# How to use
@ -26,7 +28,7 @@ This repository is a valid QMK external userspace as documented here:
You need to have a locally setup QMK and use this repo as overlay:
qmk config user.overlay_dir="/home/cullmann/data/qmk/christoph-cullmann"
qmk config user.overlay_dir="/data/home/cullmann/data/qmk/christoph-cullmann"
To use my keymaps, do for the Corne
@ -36,11 +38,14 @@ or for the Planck
qmk flash -kb planck/rev6_drop -km christoph-cullmann
or for the Ferris
qmk flash -kb ferris/sweep -km christoph-cullmann
# Foundation
Based on ideas and code from:
- https://oxey.dev/dhorf
- https://github.com/manna-harbour/miryoku
- https://github.com/getreuer/qmk-keymap
- https://github.com/filterpaper/qmk_userspace

View file

@ -15,24 +15,25 @@
// deactivate more features
#define NO_ACTION_MACRO
#define NO_ACTION_FUNCTION
#define NO_ACTION_ONESHOT
#define NO_MUSIC_MODE
// enable NKRO by default
#define FORCE_NKRO
// many settings taken from https://github.com/getreuer/qmk-keymap
// many settings taken from https://github.com/filterpaper/qmk_userspace
// don't confuse apps
#define TAP_CODE_DELAY 5
// home row mods
#define TAPPING_TERM 170
// Tap-hold settings
#define TAPPING_TERM 200
#define TAPPING_TERM_PER_KEY
#define PERMISSIVE_HOLD
#define QUICK_TAP_TERM_PER_KEY
#define PERMISSIVE_HOLD_PER_KEY
#define HOLD_ON_OTHER_KEY_PRESS_PER_KEY
// enable streak detection
#define ACHORDION_STREAK
// Input intervals
#define QUICK_TAP_TERM TAPPING_TERM - 70
#define SHIFT_TAP_TERM TAPPING_TERM - 50
#define COMBO_IDLE_MS TAPPING_TERM + 100
#define INPUT_IDLE_MS QUICK_TAP_TERM
//
// underglow configuration

View file

@ -16,88 +16,120 @@ enum my_layers {
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[_BASE] = LAYOUT(
QK_RBT, KC_V, KC_L, KC_H, KC_K, KC_Q, KC_J, KC_F, KC_O, KC_U, KC_COMM, KC_PSCR,
XXXXXXX, RALT_T(KC_S), LALT_T(KC_R), LCTL_T(KC_N), LSFT_T(KC_T), KC_W, KC_Y, RSFT_T(KC_C), RCTL_T(KC_A), LALT_T(KC_E), RALT_T(KC_I), XXXXXXX,
XXXXXXX, KC_Z, KC_X, KC_M, LGUI_T(KC_D), KC_B, KC_P, RGUI_T(KC_G), KC_QUOT, KC_SCLN, KC_DOT, XXXXXXX,
MO(_SYM), KC_SPC, MO(_NUM), MO(_NAV), KC_BSPC, MO(_FN)
KC_B, KC_F, KC_L, KC_K, KC_Q, KC_P, KC_G, KC_O, KC_U, KC_DOT,
RALT_T(KC_N), LALT_T(KC_S), LCTL_T(KC_H), LSFT_T(KC_T), LGUI_T(KC_M), RGUI_T(KC_Y), RSFT_T(KC_C), RCTL_T(KC_A), LALT_T(KC_E), RALT_T(KC_I),
KC_X, KC_V, KC_J, KC_D, KC_Z, KC_QUOT, KC_W, KC_SCLN, KC_SLSH, KC_COMM,
LT(_SYM, KC_MINS), LT(_NUM, KC_R), LT(_NAV, KC_SPC), LT(_FN, KC_EQL)
),
[_NUM] = LAYOUT(
_______, XXXXXXX, C(KC_X), C(KC_C), C(KC_V), XXXXXXX, KC_LBRC, KC_7, KC_8, KC_9, KC_RBRC, _______,
_______, KC_RALT, KC_LALT, KC_LCTL, KC_LSFT, XXXXXXX, KC_EQL, KC_4, KC_5, KC_6, KC_SLSH, _______,
_______, XXXXXXX, XXXXXXX, XXXXXXX, KC_LGUI, XXXXXXX, KC_BSLS, KC_1, KC_2, KC_3, KC_GRV, _______,
XXXXXXX, XXXXXXX, XXXXXXX, KC_MINS, KC_0, KC_DOT
XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, QK_RBT, KC_LBRC, KC_7, KC_8, KC_9, KC_RBRC,
KC_RALT, KC_LALT, KC_LCTL, KC_LSFT, KC_LGUI, KC_EQL, KC_4, KC_5, KC_6, KC_SLSH,
XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, KC_BSLS, KC_1, KC_2, KC_3, KC_GRV,
XXXXXXX, XXXXXXX, KC_MINS, KC_0
),
[_NAV] = LAYOUT(
_______, XXXXXXX, KC_PGUP, KC_UP, KC_PGDN, XXXXXXX, QK_BOOT, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, _______,
_______, KC_HOME, KC_LEFT, KC_DOWN, KC_RGHT, KC_END, XXXXXXX, KC_RSFT, KC_RCTL, KC_LALT, KC_RALT, _______,
_______, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, KC_RGUI, XXXXXXX, XXXXXXX, XXXXXXX, _______,
KC_TAB, KC_ENT, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX
XXXXXXX, KC_PGUP, KC_UP, KC_PGDN, XXXXXXX, QK_RBT, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX,
KC_HOME, KC_LEFT, KC_DOWN, KC_RGHT, KC_END, KC_RGUI, KC_RSFT, KC_RCTL, KC_LALT, KC_RALT,
XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX,
KC_TAB, KC_ENT, XXXXXXX, XXXXXXX
),
[_SYM] = LAYOUT(
_______, XXXXXXX, C(KC_X), C(KC_C), C(KC_V), XXXXXXX, KC_LCBR, KC_AMPR, KC_ASTR, KC_LPRN, KC_RCBR, _______,
_______, KC_RALT, KC_LALT, KC_LCTL, KC_LSFT, XXXXXXX, KC_PLUS, KC_DLR, KC_PERC, KC_CIRC, KC_QUES, _______,
_______, XXXXXXX, XXXXXXX, XXXXXXX, KC_LGUI, XXXXXXX, KC_PIPE, KC_EXLM, KC_AT, KC_HASH, KC_TILD, _______,
XXXXXXX, XXXXXXX, XXXXXXX, KC_UNDS, KC_LPRN, KC_RPRN
XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, QK_BOOT, KC_LCBR, KC_AMPR, KC_ASTR, KC_LPRN, KC_RCBR,
KC_RALT, KC_LALT, KC_LCTL, KC_LSFT, KC_LGUI, KC_PLUS, KC_DLR, KC_PERC, KC_CIRC, KC_QUES,
XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, KC_PIPE, KC_EXLM, KC_AT, KC_HASH, KC_TILD,
XXXXXXX, XXXXXXX, KC_UNDS, KC_RPRN
),
[_FN] = LAYOUT(
_______, KC_F12, KC_F7, KC_F8, KC_F9, XXXXXXX, QK_BOOT, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, _______,
_______, KC_F11, KC_F4, KC_F5, KC_F6, KC_DEL, XXXXXXX, KC_RSFT, KC_RCTL, KC_LALT, KC_RALT, _______,
_______, KC_F10, KC_F1, KC_F2, KC_F3, KC_INS, XXXXXXX, KC_RGUI, XXXXXXX, XXXXXXX, XXXXXXX, _______,
KC_BSPC, KC_ESC, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX
KC_F12, KC_F7, KC_F8, KC_F9, KC_PSCR, QK_BOOT, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX,
KC_F11, KC_F4, KC_F5, KC_F6, KC_DEL, KC_RGUI, KC_RSFT, KC_RCTL, KC_LALT, KC_RALT,
KC_F10, KC_F1, KC_F2, KC_F3, KC_INS, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX,
KC_BSPC, KC_ESC, XXXXXXX, XXXXXXX
)
};
// many settings taken from https://github.com/getreuer/qmk-keymap
// parts taken from https://github.com/filterpaper/qmk_userspace
#include "features/achordion.h"
// Convert 5-bit packed mod-tap modifiers to 8-bit packed MOD_MASK modifiers
#define MOD_TAP_GET_MOD_BITS(k) (((k) & 0x0f00) >> (((k) & 0x1000) ? 4 : 8))
// Basic keycode filter for tap-hold keys
#define GET_TAP_KEYCODE(k) ((k) & 0xff)
// Tap-hold decision helper macros
#define IS_LAYER_TAP(k) (IS_QK_LAYER_TAP(k) && QK_LAYER_TAP_GET_LAYER(k))
#define IS_SHORTCUT(k) (IS_QK_LAYER_TAP(k) && !QK_LAYER_TAP_GET_LAYER(k))
#define IS_MOD_TAP_SHIFT(k) (IS_QK_MOD_TAP(k) && (k) & (QK_LSFT))
#define IS_MOD_TAP_CAG(k) (IS_QK_MOD_TAP(k) && (k) & (QK_LCTL|QK_LALT|QK_LGUI))
#define IS_HOMEROW(r) (r->event.key.row == 1 || r->event.key.row == 5)
#define IS_HOMEROW_SHIFT(k, r) (IS_HOMEROW(r) && IS_MOD_TAP_SHIFT(k))
#define IS_HOMEROW_CAG(k, r) (IS_HOMEROW(r) && IS_MOD_TAP_CAG(k))
#define IS_TYPING(k) ((uint8_t)(k) <= KC_Z && last_input_activity_elapsed() < INPUT_IDLE_MS)
#define IS_UNILATERAL(r, n) ( \
(r->event.key.row == 1 && on_left_hand(n.event.key)) || \
(r->event.key.row == 5 && !on_left_hand(n.event.key)) )
static uint_fast16_t inter_keycode;
static keyrecord_t inter_record;
bool pre_process_record_user(uint16_t keycode, keyrecord_t *record) {
static bool is_pressed[UINT8_MAX];
uint16_t const tap_keycode = GET_TAP_KEYCODE(keycode);
if (record->event.pressed) {
// Press the tap keycode if the tap-hold key follows an alphabet key swiftly
if ((IS_HOMEROW_CAG(keycode, record) || IS_SHORTCUT(keycode)) && IS_TYPING(inter_keycode)) {
is_pressed[tap_keycode] = true;
record->keycode = tap_keycode;
}
// Cache incoming input for in-progress and subsequent tap-hold decisions
inter_keycode = keycode;
inter_record = *record;
}
// Release the pressed tap keycode
else if (is_pressed[tap_keycode]) {
is_pressed[tap_keycode] = false;
record->keycode = tap_keycode;
}
bool process_record_user(uint16_t keycode, keyrecord_t* record) {
if (!process_achordion(keycode, record)) { return false; }
return true;
}
void matrix_scan_user(void) {
achordion_task();
}
bool achordion_chord(uint16_t tap_hold_keycode,
keyrecord_t* tap_hold_record,
uint16_t other_keycode,
keyrecord_t* other_record) {
// follow the opposite hands rule.
return on_left_hand(tap_hold_record->event.key) !=
on_left_hand(other_record->event.key);
}
uint16_t achordion_timeout(uint16_t tap_hold_keycode) {
switch (tap_hold_keycode) {
default:
return 800; // Use a timeout of 800 ms.
}
}
uint16_t achordion_streak_chord_timeout(
uint16_t tap_hold_keycode, uint16_t next_keycode) {
// Disable streak detection on LT keys.
if (IS_QK_LAYER_TAP(tap_hold_keycode)) {
return 0;
bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record) {
// Tap the mod-tap key with an overlapping non-Shift key on the same hand
// or the shortcut key with any overlapping key
if ((IS_UNILATERAL(record, inter_record) && !IS_MOD_TAP_SHIFT(inter_keycode)) || IS_SHORTCUT(keycode)) {
record->tap.count++;
process_record(record);
return false;
}
// Otherwise, tap_hold_keycode is a mod-tap key.
const uint8_t mod = mod_config(QK_MOD_TAP_GET_MODS(tap_hold_keycode));
if ((mod & (MOD_LSFT | MOD_RSFT)) != 0) {
return 100; // A short streak timeout for Shift mod-tap keys.
} else {
return 220; // A longer timeout otherwise.
}
// Activate layer hold with another key press
else return IS_LAYER_TAP(keycode);
}
bool get_permissive_hold(uint16_t keycode, keyrecord_t *record) {
// Enable Shift with a nested key press
return IS_HOMEROW_SHIFT(keycode, record);
}
uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) {
// Shorten interval for Shift
return IS_HOMEROW_SHIFT(keycode, record) ? SHIFT_TAP_TERM : TAPPING_TERM;
}
#ifndef NO_LED
void keyboard_post_init_user(void) {
// always use the same effect
rgblight_mode_noeeprom(RGBLIGHT_MODE_BREATHING);
@ -137,3 +169,16 @@ layer_state_t layer_state_set_user(layer_state_t state) {
return state;
}
#endif
// Simplify unused magic config functions
#ifndef MAGIC_ENABLE
uint8_t mod_config(uint8_t mod) { return mod; }
uint16_t keycode_config(uint16_t keycode) { return keycode; }
#endif
// Reduce marix scanning delay
#ifndef DIRECT_PINS
void matrix_io_delay(void) { __asm__ volatile("nop\nnop\nnop\n"); }
#endif

View file

@ -1,383 +0,0 @@
// Copyright 2022-2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @file achordion.c
* @brief Achordion implementation
*
* For full documentation, see
* <https://getreuer.info/posts/keyboards/achordion>
*/
#include "achordion.h"
#if !defined(IS_QK_MOD_TAP)
// Attempt to detect out-of-date QMK installation, which would fail with
// implicit-function-declaration errors in the code below.
#error "achordion: QMK version is too old to build. Please update QMK."
#else
// Copy of the `record` and `keycode` args for the current active tap-hold key.
static keyrecord_t tap_hold_record;
static uint16_t tap_hold_keycode = KC_NO;
// Timeout timer. When it expires, the key is considered held.
static uint16_t hold_timer = 0;
// Eagerly applied mods, if any.
static uint8_t eager_mods = 0;
// Flag to determine whether another key is pressed within the timeout.
static bool pressed_another_key_before_release = false;
#ifdef ACHORDION_STREAK
// Timer for typing streak
static uint16_t streak_timer = 0;
#else
// When disabled, is_streak is never true
#define is_streak false
#endif
// Achordion's current state.
enum {
// A tap-hold key is pressed, but hasn't yet been settled as tapped or held.
STATE_UNSETTLED,
// Achordion is inactive.
STATE_RELEASED,
// Active tap-hold key has been settled as tapped.
STATE_TAPPING,
// Active tap-hold key has been settled as held.
STATE_HOLDING,
// This state is set while calling `process_record()`, which will recursively
// call `process_achordion()`. This state is checked so that we don't process
// events generated by Achordion and potentially create an infinite loop.
STATE_RECURSING,
};
static uint8_t achordion_state = STATE_RELEASED;
#ifdef ACHORDION_STREAK
static void update_streak_timer(uint16_t keycode, keyrecord_t* record) {
if (achordion_streak_continue(keycode)) {
// We use 0 to represent an unset timer, so `| 1` to force a nonzero value.
streak_timer = record->event.time | 1;
} else {
streak_timer = 0;
}
}
#endif
// Presses or releases eager_mods through process_action(), which skips the
// usual event handling pipeline. The action is considered as a mod-tap hold or
// release, with Retro Tapping if enabled.
static void process_eager_mods_action(void) {
action_t action;
action.code = ACTION_MODS_TAP_KEY(
eager_mods, QK_MOD_TAP_GET_TAP_KEYCODE(tap_hold_keycode));
process_action(&tap_hold_record, action);
}
// Calls `process_record()` with state set to RECURSING.
static void recursively_process_record(keyrecord_t* record, uint8_t state) {
achordion_state = STATE_RECURSING;
#if defined(POINTING_DEVICE_ENABLE) && defined(POINTING_DEVICE_AUTO_MOUSE_ENABLE)
int8_t mouse_key_tracker = get_auto_mouse_key_tracker();
#endif
process_record(record);
#if defined(POINTING_DEVICE_ENABLE) && defined(POINTING_DEVICE_AUTO_MOUSE_ENABLE)
set_auto_mouse_key_tracker(mouse_key_tracker);
#endif
achordion_state = state;
}
// Sends hold press event and settles the active tap-hold key as held.
static void settle_as_hold(void) {
if (eager_mods) {
// If eager mods are being applied, nothing needs to be done besides
// updating the state.
dprintln("Achordion: Settled eager mod as hold.");
achordion_state = STATE_HOLDING;
} else {
// Create hold press event.
dprintln("Achordion: Plumbing hold press.");
recursively_process_record(&tap_hold_record, STATE_HOLDING);
}
}
// Sends tap press and release and settles the active tap-hold key as tapped.
static void settle_as_tap(void) {
if (eager_mods) { // Clear eager mods if set.
#if defined(RETRO_TAPPING) || defined(RETRO_TAPPING_PER_KEY)
#ifdef DUMMY_MOD_NEUTRALIZER_KEYCODE
neutralize_flashing_modifiers(get_mods());
#endif // DUMMY_MOD_NEUTRALIZER_KEYCODE
#endif // defined(RETRO_TAPPING) || defined(RETRO_TAPPING_PER_KEY)
tap_hold_record.event.pressed = false;
// To avoid falsely triggering Retro Tapping, process eager mods release as
// a regular mods release rather than a mod-tap release.
action_t action;
action.code = ACTION_MODS(eager_mods);
process_action(&tap_hold_record, action);
eager_mods = 0;
}
dprintln("Achordion: Plumbing tap press.");
tap_hold_record.event.pressed = true;
tap_hold_record.tap.count = 1; // Revise event as a tap.
tap_hold_record.tap.interrupted = true;
// Plumb tap press event.
recursively_process_record(&tap_hold_record, STATE_TAPPING);
send_keyboard_report();
#if TAP_CODE_DELAY > 0
wait_ms(TAP_CODE_DELAY);
#endif // TAP_CODE_DELAY > 0
dprintln("Achordion: Plumbing tap release.");
tap_hold_record.event.pressed = false;
// Plumb tap release event.
recursively_process_record(&tap_hold_record, STATE_TAPPING);
}
bool process_achordion(uint16_t keycode, keyrecord_t* record) {
// Don't process events that Achordion generated.
if (achordion_state == STATE_RECURSING) {
return true;
}
// Determine whether the current event is for a mod-tap or layer-tap key.
const bool is_mt = IS_QK_MOD_TAP(keycode);
const bool is_tap_hold = is_mt || IS_QK_LAYER_TAP(keycode);
// Check that this is a normal key event, don't act on combos.
const bool is_key_event = IS_KEYEVENT(record->event);
// Event while no tap-hold key is active.
if (achordion_state == STATE_RELEASED) {
if (is_tap_hold && record->tap.count == 0 && record->event.pressed &&
is_key_event) {
// A tap-hold key is pressed and considered by QMK as "held".
const uint16_t timeout = achordion_timeout(keycode);
if (timeout > 0) {
achordion_state = STATE_UNSETTLED;
// Save info about this key.
tap_hold_keycode = keycode;
tap_hold_record = *record;
hold_timer = record->event.time + timeout;
pressed_another_key_before_release = false;
eager_mods = 0;
if (is_mt) { // Apply mods immediately if they are "eager."
const uint8_t mod = mod_config(QK_MOD_TAP_GET_MODS(keycode));
if (
#if defined(CAPS_WORD_ENABLE) && defined(CAPS_WORD_INVERT_ON_SHIFT)
// Since eager mods bypass normal event handling, eager Shift does
// not work with CAPS_WORD_INVERT_ON_SHIFT. So if this option is
// enabled, we don't apply Shift eagerly when Caps Word is on.
!(is_caps_word_on() && (mod & MOD_LSFT) != 0) &&
#endif // defined(CAPS_WORD_ENABLE) && defined(CAPS_WORD_INVERT_ON_SHIFT)
achordion_eager_mod(mod)) {
eager_mods = mod;
process_eager_mods_action();
}
}
dprintf("Achordion: Key 0x%04X pressed.%s\n", keycode,
eager_mods ? " Set eager mods." : "");
return false; // Skip default handling.
}
}
#ifdef ACHORDION_STREAK
update_streak_timer(keycode, record);
#endif
return true; // Otherwise, continue with default handling.
} else if (record->event.pressed && tap_hold_keycode != keycode) {
// Track whether another key was pressed while using a tap-hold key.
pressed_another_key_before_release = true;
}
// Release of the active tap-hold key.
if (keycode == tap_hold_keycode && !record->event.pressed) {
if (eager_mods) {
dprintln("Achordion: Key released. Clearing eager mods.");
tap_hold_record.event.pressed = false;
process_eager_mods_action();
} else if (achordion_state == STATE_HOLDING) {
dprintln("Achordion: Key released. Plumbing hold release.");
tap_hold_record.event.pressed = false;
// Plumb hold release event.
recursively_process_record(&tap_hold_record, STATE_RELEASED);
} else if (!pressed_another_key_before_release) {
// No other key was pressed between the press and release of the tap-hold
// key, plumb a hold press and then a release.
dprintln("Achordion: Key released. Plumbing hold press and release.");
recursively_process_record(&tap_hold_record, STATE_HOLDING);
tap_hold_record.event.pressed = false;
recursively_process_record(&tap_hold_record, STATE_RELEASED);
} else {
dprintln("Achordion: Key released.");
}
achordion_state = STATE_RELEASED;
tap_hold_keycode = KC_NO;
return false;
}
if (achordion_state == STATE_UNSETTLED && record->event.pressed) {
#ifdef ACHORDION_STREAK
const uint16_t s_timeout =
achordion_streak_chord_timeout(tap_hold_keycode, keycode);
const bool is_streak =
streak_timer && s_timeout &&
!timer_expired(record->event.time, (streak_timer + s_timeout));
#endif
// Press event occurred on a key other than the active tap-hold key.
// If the other key is *also* a tap-hold key and considered by QMK to be
// held, then we settle the active key as held. This way, things like
// chording multiple home row modifiers will work, but let's our logic
// consider simply a single tap-hold key as "active" at a time.
//
// Otherwise, we call `achordion_chord()` to determine whether to settle the
// tap-hold key as tapped vs. held. We implement the tap or hold by plumbing
// events back into the handling pipeline so that QMK features and other
// user code can see them. This is done by calling `process_record()`, which
// in turn calls most handlers including `process_record_user()`.
if (!is_streak &&
(!is_key_event || (is_tap_hold && record->tap.count == 0) ||
achordion_chord(tap_hold_keycode, &tap_hold_record, keycode,
record))) {
settle_as_hold();
#ifdef REPEAT_KEY_ENABLE
// Edge case involving LT + Repeat Key: in a sequence of "LT down, other
// down" where "other" is on the other layer in the same position as
// Repeat or Alternate Repeat, the repeated keycode is set instead of the
// the one on the switched-to layer. Here we correct that.
if (get_repeat_key_count() != 0 && IS_QK_LAYER_TAP(tap_hold_keycode)) {
record->keycode = KC_NO; // Forget the repeated keycode.
clear_weak_mods();
}
#endif // REPEAT_KEY_ENABLE
} else {
settle_as_tap();
#ifdef ACHORDION_STREAK
update_streak_timer(keycode, record);
if (is_streak && is_key_event && is_tap_hold && record->tap.count == 0) {
// If we are in a streak and resolved the current tap-hold key as a tap
// consider the next tap-hold key as active to be resolved next.
update_streak_timer(tap_hold_keycode, &tap_hold_record);
const uint16_t timeout = achordion_timeout(keycode);
tap_hold_keycode = keycode;
tap_hold_record = *record;
hold_timer = record->event.time + timeout;
achordion_state = STATE_UNSETTLED;
pressed_another_key_before_release = false;
return false;
}
#endif
}
recursively_process_record(record, achordion_state); // Re-process event.
return false; // Block the original event.
}
#ifdef ACHORDION_STREAK
// update idle timer on regular keys event
update_streak_timer(keycode, record);
#endif
return true;
}
void achordion_task(void) {
if (achordion_state == STATE_UNSETTLED &&
timer_expired(timer_read(), hold_timer)) {
settle_as_hold(); // Timeout expired, settle the key as held.
}
#ifdef ACHORDION_STREAK
#define MAX_STREAK_TIMEOUT 800
if (streak_timer &&
timer_expired(timer_read(), (streak_timer + MAX_STREAK_TIMEOUT))) {
streak_timer = 0; // Expired.
}
#endif
}
// Returns true if `pos` on the left hand of the keyboard, false if right.
static bool on_left_hand(keypos_t pos) {
#ifdef SPLIT_KEYBOARD
return pos.row < MATRIX_ROWS / 2;
#else
return (MATRIX_COLS > MATRIX_ROWS) ? pos.col < MATRIX_COLS / 2
: pos.row < MATRIX_ROWS / 2;
#endif
}
bool achordion_opposite_hands(const keyrecord_t* tap_hold_record,
const keyrecord_t* other_record) {
return on_left_hand(tap_hold_record->event.key) !=
on_left_hand(other_record->event.key);
}
// By default, use the BILATERAL_COMBINATIONS rule to consider the tap-hold key
// "held" only when it and the other key are on opposite hands.
__attribute__((weak)) bool achordion_chord(uint16_t tap_hold_keycode,
keyrecord_t* tap_hold_record,
uint16_t other_keycode,
keyrecord_t* other_record) {
return achordion_opposite_hands(tap_hold_record, other_record);
}
// By default, the timeout is 1000 ms for all keys.
__attribute__((weak)) uint16_t achordion_timeout(uint16_t tap_hold_keycode) {
return 1000;
}
// By default, Shift and Ctrl mods are eager, and Alt and GUI are not.
__attribute__((weak)) bool achordion_eager_mod(uint8_t mod) {
return (mod & (MOD_LALT | MOD_LGUI)) == 0;
}
#ifdef ACHORDION_STREAK
__attribute__((weak)) bool achordion_streak_continue(uint16_t keycode) {
// If any mods other than shift or AltGr are held, don't continue the streak
if (get_mods() & (MOD_MASK_CG | MOD_BIT_LALT)) return false;
// This function doesn't get called for holds, so convert to tap version of
// keycodes
if (IS_QK_MOD_TAP(keycode)) keycode = QK_MOD_TAP_GET_TAP_KEYCODE(keycode);
if (IS_QK_LAYER_TAP(keycode)) keycode = QK_LAYER_TAP_GET_TAP_KEYCODE(keycode);
// Regular letters and punctuation continue the streak.
if (keycode >= KC_A && keycode <= KC_Z) return true;
switch (keycode) {
case KC_DOT:
case KC_COMMA:
case KC_QUOTE:
case KC_SPACE:
return true;
}
// All other keys end the streak
return false;
}
__attribute__((weak)) uint16_t achordion_streak_chord_timeout(
uint16_t tap_hold_keycode, uint16_t next_keycode) {
return achordion_streak_timeout(tap_hold_keycode);
}
__attribute__((weak)) uint16_t
achordion_streak_timeout(uint16_t tap_hold_keycode) {
return 200;
}
#endif
#endif // version check

View file

@ -1,193 +0,0 @@
// Copyright 2022-2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @file achordion.h
* @brief Achordion: Customizing the tap-hold decision.
*
* Overview
* --------
*
* This library customizes when tap-hold keys are considered held vs. tapped
* based on the next pressed key, like Manna Harbour's Bilateral Combinations or
* ZMK's positional hold. The library works on top of QMK's existing tap-hold
* implementation. You define mod-tap and layer-tap keys as usual and use
* Achordion to fine-tune the behavior.
*
* When QMK settles a tap-hold key as held, Achordion intercepts the event.
* Achordion then revises the event as a tap or passes it along as a hold:
*
* * Chord condition: On the next key press, a customizable `achordion_chord()`
* function is called, which takes the tap-hold key and the next key pressed
* as args. When the function returns true, the tap-hold key is settled as
* held, and otherwise as tapped.
*
* * Timeout: If no other key press occurs within a timeout, the tap-hold key
* is settled as held. This is customizable with `achordion_timeout()`.
*
* Achordion only changes the behavior when QMK considered the key held. It
* changes some would-be holds to taps, but no taps to holds.
*
* @note Some QMK features handle events before the point where Achordion can
* intercept them, particularly: Combos, Key Lock, and Dynamic Macros. It's
* still possible to use these features and Achordion in your keymap, but beware
* they might behave poorly when used simultaneously with tap-hold keys.
*
*
* For full documentation, see
* <https://getreuer.info/posts/keyboards/achordion>
*/
#pragma once
#include "quantum.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Handler function for Achordion.
*
* Call this function from `process_record_user()` as
*
* #include "features/achordion.h"
*
* bool process_record_user(uint16_t keycode, keyrecord_t* record) {
* if (!process_achordion(keycode, record)) { return false; }
* // Your macros...
* return true;
* }
*/
bool process_achordion(uint16_t keycode, keyrecord_t* record);
/**
* Matrix task function for Achordion.
*
* Call this function from `matrix_scan_user()` as
*
* void matrix_scan_user(void) {
* achordion_task();
* }
*/
void achordion_task(void);
/**
* Optional callback to customize which key chords are considered "held".
*
* In your keymap.c, define the callback
*
* bool achordion_chord(uint16_t tap_hold_keycode,
* keyrecord_t* tap_hold_record,
* uint16_t other_keycode,
* keyrecord_t* other_record) {
* // Conditions...
* }
*
* This callback is called if while `tap_hold_keycode` is pressed,
* `other_keycode` is pressed. Return true if the tap-hold key should be
* considered held, or false to consider it tapped.
*
* @param tap_hold_keycode Keycode of the tap-hold key.
* @param tap_hold_record keyrecord_t from the tap-hold press event.
* @param other_keycode Keycode of the other key.
* @param other_record keyrecord_t from the other key's press event.
* @return True if the tap-hold key should be considered held.
*/
bool achordion_chord(uint16_t tap_hold_keycode, keyrecord_t* tap_hold_record,
uint16_t other_keycode, keyrecord_t* other_record);
/**
* Optional callback to define a timeout duration per keycode.
*
* In your keymap.c, define the callback
*
* uint16_t achordion_timeout(uint16_t tap_hold_keycode) {
* // ...
* }
*
* The callback determines Achordion's timeout duration for `tap_hold_keycode`
* in units of milliseconds. The timeout be in the range 0 to 32767 ms (upper
* bound is due to 16-bit timer limitations). Use a timeout of 0 to bypass
* Achordion.
*
* @param tap_hold_keycode Keycode of the tap-hold key.
* @return Timeout duration in milliseconds in the range 0 to 32767.
*/
uint16_t achordion_timeout(uint16_t tap_hold_keycode);
/**
* Optional callback defining which mods are "eagerly" applied.
*
* This callback defines which mods are "eagerly" applied while a mod-tap
* key is still being settled. This is helpful to reduce delay particularly when
* using mod-tap keys with an external mouse.
*
* Define this callback in your keymap.c. The default callback is eager for
* Shift and Ctrl, and not for Alt and GUI:
*
* bool achordion_eager_mod(uint8_t mod) {
* return (mod & (MOD_LALT | MOD_LGUI)) == 0;
* }
*
* @note `mod` should be compared with `MOD_` prefixed codes, not `KC_` codes,
* described at <https://docs.qmk.fm/mod_tap>.
*
* @param mod Modifier `MOD_` code.
* @return True if the modifier should be eagerly applied.
*/
bool achordion_eager_mod(uint8_t mod);
/**
* Returns true if the args come from keys on opposite hands.
*
* @param tap_hold_record keyrecord_t from the tap-hold key's event.
* @param other_record keyrecord_t from the other key's event.
* @return True if the keys are on opposite hands.
*/
bool achordion_opposite_hands(const keyrecord_t* tap_hold_record,
const keyrecord_t* other_record);
/**
* Suppress tap-hold mods within a *typing streak* by defining
* ACHORDION_STREAK. This can help preventing accidental mod
* activation when performing a fast tapping sequence.
* This is inspired by
* https://sunaku.github.io/home-row-mods.html#typing-streaks
*
* Enable with:
*
* #define ACHORDION_STREAK
*
* Adjust the maximum time between key events before modifiers can be enabled
* by defining the following callback in your keymap.c:
*
* uint16_t achordion_streak_chord_timeout(
* uint16_t tap_hold_keycode, uint16_t next_keycode) {
* return 200; // Default of 200 ms.
* }
*/
#ifdef ACHORDION_STREAK
uint16_t achordion_streak_chord_timeout(uint16_t tap_hold_keycode,
uint16_t next_keycode);
bool achordion_streak_continue(uint16_t keycode);
/** @deprecated Use `achordion_streak_chord_timeout()` instead. */
uint16_t achordion_streak_timeout(uint16_t tap_hold_keycode);
#endif
#ifdef __cplusplus
}
#endif

View file

@ -14,16 +14,16 @@ static bool on_left_hand(keypos_t pos)
// layout helper macro, we just use 42 keys
#undef LAYOUT
#define LAYOUT(\
K00, K01, K02, K03, K04, K05, K06, K07, K08, K09, K10, K11,\
K12, K13, K14, K15, K16, K17, K18, K19, K20, K21, K22, K23,\
K24, K25, K26, K27, K28, K29, K30, K31, K32, K33, K34, K35,\
K36, K37, K38, K39, K40, K41\
K00, K01, K02, K03, K04, K05, K06, K07, K08, K09,\
K10, K11, K12, K13, K14, K15, K16, K17, K18, K19,\
K20, K21, K22, K23, K24, K25, K26, K27, K28, K29,\
K30, K31, K32, K33\
)\
LAYOUT_split_3x6_3(\
K00, K01, K02, K03, K04, K05, K06, K07, K08, K09, K10, K11,\
K12, K13, K14, K15, K16, K17, K18, K19, K20, K21, K22, K23,\
K24, K25, K26, K27, K28, K29, K30, K31, K32, K33, K34, K35,\
K36, K37, K38, K39, K40, K41\
KC_NO, K00, K01, K02, K03, K04, K05, K06, K07, K08, K09, KC_NO,\
KC_NO, K10, K11, K12, K13, K14, K15, K16, K17, K18, K19, KC_NO,\
KC_NO, K20, K21, K22, K23, K24, K25, K26, K27, K28, K29, KC_NO,\
KC_NO, K30, K31, K32, K33, KC_NO\
)
// our shared 42 keys keymap

View file

@ -0,0 +1,9 @@
/**
* SPDX-FileCopyrightText: 2024 Christoph Cullmann <christoph@cullmann.io>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
// our shared config parts
#include "common/config.h"

View file

@ -0,0 +1,33 @@
/**
* SPDX-FileCopyrightText: 2024 Christoph Cullmann <christoph@cullmann.io>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include QMK_KEYBOARD_H
// Returns true if `pos` on the left hand of the keyboard, false if right.
static bool on_left_hand(keypos_t pos)
{
return pos.row < MATRIX_ROWS / 2;
}
// layout helper macro, we just use 42 keys
#undef LAYOUT
#define LAYOUT(\
K00, K01, K02, K03, K04, K05, K06, K07, K08, K09,\
K10, K11, K12, K13, K14, K15, K16, K17, K18, K19,\
K20, K21, K22, K23, K24, K25, K26, K27, K28, K29,\
K30, K31, K32, K33\
)\
LAYOUT_split_3x5_2(\
K00, K01, K02, K03, K04, K05, K06, K07, K08, K09,\
K10, K11, K12, K13, K14, K15, K16, K17, K18, K19,\
K20, K21, K22, K23, K24, K25, K26, K27, K28, K29,\
K30, K31, K32, K33\
)
// no led
#define NO_LED
// our shared 42 keys keymap
#include "common/keymap.h"

View file

@ -0,0 +1,3 @@
# include common settings
ROOT_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
include ${ROOT_DIR}../../../../rules.mk

View file

@ -8,10 +8,6 @@
// our shared config parts
#include "common/config.h"
//
// audio configuration
//
// fix startup sound
#define AUDIO_INIT_DELAY

View file

@ -14,16 +14,16 @@ static bool on_left_hand(keypos_t pos)
// layout helper macro, we just use 42 keys
#undef LAYOUT
#define LAYOUT(\
K00, K01, K02, K03, K04, K05, K06, K07, K08, K09, K10, K11,\
K12, K13, K14, K15, K16, K17, K18, K19, K20, K21, K22, K23,\
K24, K25, K26, K27, K28, K29, K30, K31, K32, K33, K34, K35,\
K36, K37, K38, K39, K40, K41\
K00, K01, K02, K03, K04, K05, K06, K07, K08, K09,\
K10, K11, K12, K13, K14, K15, K16, K17, K18, K19,\
K20, K21, K22, K23, K24, K25, K26, K27, K28, K29,\
K30, K31, K32, K33\
)\
LAYOUT_ortho_4x12(\
K00, K01, K02, K03, K04, K05, K06, K07, K08, K09, K10, K11,\
K12, K13, K14, K15, K16, K17, K18, K19, K20, K21, K22, K23,\
K24, K25, K26, K27, K28, K29, K30, K31, K32, K33, K34, K35,\
KC_NO, KC_NO, KC_NO, K36, K37, K38, K39, K40, K41, KC_NO, KC_NO, KC_NO\
K00, K01, K02, K03, K04, KC_NO, KC_NO, K05, K06, K07, K08, K09,\
K10, K11, K12, K13, K14, KC_NO, KC_NO, K15, K16, K17, K18, K19,\
K20, K21, K22, K23, K24, KC_NO, KC_NO, K25, K26, K27, K28, K29,\
KC_NO, KC_NO, KC_NO, K30, K31, KC_NO, KC_NO, K32, K33, KC_NO, KC_NO, KC_NO\
)
// our shared 42 keys keymap

View file

@ -1,6 +1,9 @@
# Enable N-Key Rollover
NKRO_ENABLE = yes
# needed for keycode in keyrecord_t
REPEAT_KEY_ENABLE = yes
# less features we don't use
COMMAND_ENABLE = no
CONSOLE_ENABLE = no
@ -10,9 +13,6 @@ MOUSEKEY_ENABLE = no
MUSIC_ENABLE = no
SPACE_CADET_ENABLE = no
# add achordion to improve home row modifiers
SRC += features/achordion.c
# add bongocat & luna for OLEDs
ifeq ($(strip $(OLED_ENABLE)), yes)
SRC += features/oled_bongocat.c