Commit ac778dd74275

Vincent Demeester <vincent@sbr.pm>
2025-11-19 18:11:15
feat(moonlander): Implement numword smart layer
- Enable sticky number layer activation like capsword for numbers - Replace QK_AREP with smart tap/hold numword key across all layouts - Support automatic layer deactivation after typing non-numeric keys Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent e05079d
keyboards/moonlander/config/config.h
@@ -28,6 +28,9 @@
 #define COMBO_TERM 40
 #define CHORDAL_HOLD
 
+// Numword configuration
+#define NUMWORD_TIMEOUT 5000  // Time in ms before numword auto-disables (optional)
+
 /* #define DOUBLE_TAP_SHIFT_TURNS_ON_CAPS_WORD 1 */
 /* #define CAPS_WORD_INVERT_ON_SHIFT 1 */
 
keyboards/moonlander/config/keymap.c
@@ -22,6 +22,7 @@
 #include "version.h"
 #include "keymap_us.h"
 #include "keymap_us_international_linux.h"
+#include "layermodes.h"
 
 enum layers {
   BEPO,
@@ -46,6 +47,7 @@ enum custom_keycodes {
   FR_E_GRAVE_CAPS,
   FR_A_GRAVE,
   FR_A_GRAVE_CAPS,
+  NUMWORD,  // Numword layer activation
 };
 
 const key_override_t circ_exclamation_override = ko_make_with_layers(MOD_MASK_SHIFT, KC_CIRC, KC_EXLM, 1 << BEPO);
@@ -220,13 +222,13 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
 		  // $  " « » ( ) @ + - / * = %
 		  // shift: # 1 2 3 4 5 6 7 8 9 0 ° `
 		  // ralt : – — < > [ ] ^ ± − ÷ × ≠ ‰
-		  // shift+ralt : ¶ „ “ ” ≤ ≥ _ ¬ ¼ ½ ¾ ′ ″
+		  // shift+ralt : ¶ „ " " ≤ ≥ _ ¬ ¼ ½ ¾ ′ ″
 		  // FIXME: should I invert ?
 		  KC_DLR,  FR_DQUO,    US_LDAQ,    US_RDAQ,  KC_LPRN,    KC_RPRN,    XXXXXXX,           XXXXXXX, KC_AT,    KC_PLUS,    KC_PMNS,    KC_PSLS,    KC_PAST,    KC_PERC,
 		  KC_TAB,  KC_B,    FR_E_AIGU,    KC_P,    KC_O,    FR_E_GRAVE,    XXXXXXX,           XXXXXXX, KC_CIRC,    KC_V,    KC_D,    KC_L,    KC_J,    KC_Z,
 		  LT(NUMB, KC_EQL),  HM_GUI_A,    HM_ALT_U,    HM_SFT_I,    HM_CTL_E,    HM_HYP_COMM,    XXXXXXX,           XXXXXXX, HM_HYP_C,    HM_CTL_T,    HM_SFT_S,    HM_ALT_R,    HM_GUI_N, LT(SYMB,KC_M),
 		  KC_GRV,  FR_A_GRAVE,    KC_Y,    KC_X,    KC_DOT,    KC_K,                                FR_QUOT,    KC_Q,    KC_G, KC_H,  KC_F, KC_W,
-		  XXXXXXX,XXXXXXX,XXXXXXX,XXXXXXX, LT(MODS, KC_DEL),  QK_REP,               QK_AREP,      KC_RALT, XXXXXXX,XXXXXXX,XXXXXXX,XXXXXXX,
+		  XXXXXXX,XXXXXXX,XXXXXXX,XXXXXXX, LT(MODS, KC_DEL),  QK_REP,               NUMWORD,      KC_RALT, XXXXXXX,XXXXXXX,XXXXXXX,XXXXXXX,
 		  LT(NUMB, KC_SPC),  LT(NAVI,KC_BSPC), XXXXXXX,           XXXXXXX,  OS_LSFT,  LT(SYMB, KC_ENT)
 		  ),
   [ERGL] = LAYOUT(
@@ -234,7 +236,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
 		  KC_TAB,  KC_Q,    KC_W,    KC_E,    KC_R,    KC_T,    XXXXXXX,           XXXXXXX, KC_Y,    KC_U,    KC_I,    KC_O,    KC_P,    KC_LBRC,
 		  KC_EQL,  HM_GUI_A,    HM_ALT_S,    HM_SFT_D,    HM_CTL_F,    HM_HYP_G,    XXXXXXX,           XXXXXXX, HM_HYP_H,    HM_CTL_J,    HM_SFT_K,    HM_ALT_L,    HM_GUI_SCLN, KC_QUOT,
 		  KC_GRV,  KC_Z,    KC_X,    KC_C,    KC_V,    KC_B,                                KC_N,    KC_M,    KC_COMM, KC_DOT,  KC_SLSH, KC_RBRC,
-		  XXXXXXX,XXXXXXX,XXXXXXX,XXXXXXX, KC_DEL,  QK_REP,               QK_AREP,      KC_RALT, XXXXXXX,XXXXXXX,XXXXXXX,XXXXXXX,
+		  XXXXXXX,XXXXXXX,XXXXXXX,XXXXXXX, KC_DEL,  QK_REP,               NUMWORD,      KC_RALT, XXXXXXX,XXXXXXX,XXXXXXX,XXXXXXX,
 		  LT(NUMB, KC_SPC),  LT(NAVI,KC_BSPC), XXXXXXX,           XXXXXXX,  OS_LSFT,  LT(SYMB, KC_ENT)
 		  ),
   [QWER] = LAYOUT(
@@ -242,7 +244,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
 		  KC_TAB,  KC_Q,    KC_W,    KC_E,    KC_R,    KC_T,    XXXXXXX,           XXXXXXX, KC_Y,    KC_U,    KC_I,    KC_O,    KC_P,    KC_LBRC,
 		  KC_EQL,  HM_GUI_A,    HM_ALT_S,    HM_SFT_D,    HM_CTL_F,    HM_HYP_G,    XXXXXXX,           XXXXXXX, HM_HYP_H,    HM_CTL_J,    HM_SFT_K,    HM_ALT_L,    HM_GUI_SCLN, KC_QUOT,
 		  KC_GRV,  KC_Z,    KC_X,    KC_C,    KC_V,    KC_B,                                KC_N,    KC_M,    KC_COMM, KC_DOT,  KC_SLSH, KC_RBRC,
-		  XXXXXXX,XXXXXXX,XXXXXXX,XXXXXXX, KC_DEL,  QK_REP,               QK_AREP,      KC_RALT, XXXXXXX,XXXXXXX,XXXXXXX,XXXXXXX,
+		  XXXXXXX,XXXXXXX,XXXXXXX,XXXXXXX, KC_DEL,  QK_REP,               NUMWORD,      KC_RALT, XXXXXXX,XXXXXXX,XXXXXXX,XXXXXXX,
 		  LT(NUMB, KC_SPC),  LT(NAVI,KC_BSPC), XXXXXXX,           XXXXXXX,  OS_LSFT,  LT(SYMB, KC_ENT)
 		  ),
 
@@ -292,6 +294,18 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
 bool process_record_user(uint16_t keycode, keyrecord_t *record) {
   uint8_t current_layer = get_highest_layer(layer_state);
   uint8_t mod_state = get_mods();
+
+  // Process numword before other logic
+  if (!process_num_word(keycode, record)) {
+    return false;
+  }
+
+  // Handle NUMWORD separately since it needs both press and release
+  if (keycode == NUMWORD) {
+    process_num_word_activation(NUMB, record);
+    return false;
+  }
+
   if (record->event.pressed) {
     switch (keycode) {
       // FR_DQUO on bépo layout
keyboards/moonlander/config/layermodes.c
@@ -0,0 +1,99 @@
+// Numword layer implementation
+// Based on https://github.com/treeman/qmk_firmware/blob/master/keyboards/ferris/keymaps/treeman/layermodes.c
+// and https://www.jonashietala.se/blog/2022/09/06/the_current_t-34_keyboard_layout/#Numbers
+
+#include QMK_KEYBOARD_H
+#include "layermodes.h"
+
+static uint16_t num_word_timer;
+static bool _num_word_enabled = false;
+static uint8_t _num_word_layer = 0;
+
+void enable_num_word(uint8_t layer) {
+    _num_word_enabled = true;
+    _num_word_layer = layer;
+    layer_on(layer);
+}
+
+void disable_num_word(uint8_t layer) {
+    _num_word_enabled = false;
+    layer_off(layer);
+}
+
+bool is_num_word_enabled(void) {
+    return _num_word_enabled;
+}
+
+void process_num_word_activation(uint8_t layer, const keyrecord_t *record) {
+    if (record->event.pressed) {
+        layer_on(layer);
+        num_word_timer = timer_read();
+    } else {
+        if (timer_elapsed(num_word_timer) < TAPPING_TERM) {
+            // Tapped, enable numword
+            _num_word_enabled = true;
+            _num_word_layer = layer;
+        } else {
+            // Held, just turn off the layer
+            layer_off(layer);
+        }
+    }
+}
+
+// Returns true if numword should remain active after pressing this key
+static bool is_num_word_key(uint16_t keycode) {
+    switch (keycode) {
+        // Numbers
+        case KC_1 ... KC_0:
+        case KC_P1 ... KC_P0:
+        // Numpad operators
+        case KC_PAST:  // *
+        case KC_PSLS:  // /
+        case KC_PMNS:  // -
+        case KC_PPLS:  // +
+        case KC_PDOT:  // .
+        case KC_PCMM:  // ,
+        case KC_PEQL:  // =
+        // Other operators and symbols commonly used with numbers
+        case KC_PLUS:
+        case KC_MINS:
+        case KC_EQL:
+        case KC_PERC:
+        case KC_DOT:
+        case KC_COMM:
+        case KC_COLN:
+        case KC_UNDS:
+        // Special keys
+        case KC_BSPC:
+        case KC_DEL:
+        case KC_ENT:
+        case KC_SPC:
+        case QK_REP:   // Repeat key
+        case QK_AREP:  // Alternate repeat key
+        // x for hexadecimal
+        case KC_X:
+        // Modifiers (so they don't disable numword)
+        case KC_LSFT ... KC_RGUI:
+        case OS_LSFT ... OS_RGUI:
+            return true;
+        default:
+            return false;
+    }
+}
+
+bool process_num_word(uint16_t keycode, const keyrecord_t *record) {
+    if (!_num_word_enabled) {
+        return true;
+    }
+
+    if (!record->event.pressed) {
+        return true;
+    }
+
+    // Check if this key should keep numword active
+    if (!is_num_word_key(keycode)) {
+        disable_num_word(_num_word_layer);
+    }
+
+    return true;
+}
keyboards/moonlander/config/layermodes.h
@@ -0,0 +1,17 @@
+// Numword layer mode header
+
+#pragma once
+
+#include QMK_KEYBOARD_H
+
+// Enable/disable numword mode with specific layer
+void enable_num_word(uint8_t layer);
+void disable_num_word(uint8_t layer);
+bool is_num_word_enabled(void);
+
+// Process numword activation (should be called from custom keycode handler)
+void process_num_word_activation(uint8_t layer, const keyrecord_t *record);
+
+// Process numword logic (should be called from process_record_user)
+// Returns true to continue processing, false to stop
+bool process_num_word(uint16_t keycode, const keyrecord_t *record);
keyboards/moonlander/config/rules.mk
@@ -11,4 +11,7 @@ SPACE_CADET_ENABLE = no
 TAP_DANCE_ENABLE = yes
 LAYER_LOCK_ENABLE = yes
 
+# Include numword layer modes
+SRC += layermodes.c
+
 EXTRAFLAGS += -flto
CLAUDE.md
@@ -157,6 +157,16 @@ cd tools/<tool-name>
 go test ./...
 ```
 
+### Keyboard Firmware
+
+```bash
+# Build Moonlander QMK firmware
+cd keyboards/moonlander && ./go.sh build
+
+# Build eyelash_corne ZMK firmware (if applicable)
+cd keyboards/eyelash_corne && <build command>
+```
+
 ## Special Notes
 
 - The repository uses XDG base directories for Nix configuration (enabled via `use-xdg-base-directories = true`)