A highly customized QMK keymap for the Lily58 split keyboard featuring dual Pimoroni trackball integration with advanced mouse emulation, adaptive cursor scaling, and dynamic layer management.
- Keyboard: Lily58 Rev1 split keyboard
- MCU: RP2040 (both halves)
- Pointing Devices: Pimoroni trackball modules on both left and right halves
- Communication: I2C for trackballs, serial/SPI for split communication
- Dynamic acceleration: Cursor speed automatically adjusts based on movement velocity
- Growth Factor: Configurable multiplier (default: 8x, adjustable via keycodes)
- Momentum smoothing: 0.06 exponential moving average for fluid motion
- Scale range: 0.0001x to 64x maximum scaling
- Hardware acceleration: Utilizes RP2040's built-in FPU for floating-point calculations
-
Standard Mouse Mode (Default)
- Full precision cursor control with adaptive scaling
- Independent operation of left and right trackballs
-
Arrow Key Emulation Mode
- Converts trackball movement to arrow key presses
- Momentum factor: 0.99 (smoothing)
- Step threshold: 6 pixels per arrow key tap
- Ideal for text navigation and menu selection
-
Scroll Wheel Emulation Mode
- Horizontal and vertical scrolling
- Divisor: 8.0 (configurable speed)
- Smooth fractional accumulation
- Natural scroll direction (drag-to-scroll metaphor)
- Double-tap detection: Tap trackball button twice within 400ms to enter Arrow Mode
- Press-and-hold: Hold trackball button for 400ms to enter Scroll Mode
- Toggle off: Single tap when in any mode returns to Standard Mode
- Visual feedback: RGB LED indicates current mode per trackball
- Blue: Standard Mouse Mode
- Red: Arrow Key Emulation
- Green: Scroll Emulation
Dynamic RGB feedback on both trackball modules:
| State | Color | Condition |
|---|---|---|
| Base Layer (Normal) | Blue | Default state, BTN_SWAP off |
| Base Layer (Swapped) | White | BTN_SWAP enabled |
| Layer 1 (Lower) | Red | Navigation/numpad layer active |
| Layer 2 (Upper) | Green | Function/system control layer active |
| Layer 3 (Auto Mouse) | Yellow | Auto mouse layer active |
| Layer 4 (Settings) | White/Blue | Settings layer (varies with BTN_SWAP) |
| Caps Lock Active | Violet | Caps Lock on (layer 0 only) |
- Layer 0: Base QWERTY with home row mods
- Layer 1: Navigation, numpad, media controls
- Layer 2: Window management, browser controls, function keys
- Layer 3: Auto Mouse Layer (automatic activation)
- Layer 4: Settings and configuration
- Tap-or-hold behavior: 200ms delay before layer activation
- Double-tap prevention: Smart handling of rapid key presses
- Delayed release: 200ms timeout after key release to prevent sticky layers
- Shared timers: Efficient timer management across similar keycodes
- Automatic activation: Layer 3 enables when trackball movement detected
- Timeout: 1500ms of inactivity returns to previous layer
- Manual toggle: Can be enabled/disabled via keycode or combo (5+6)
- Activity tracking: Monitors arrow keys, mouse buttons, and trackball movement
- Limiter: 500ms minimum between timer updates to reduce overhead
Dynamically remaps dual-function keys based on workflow:
Normal Mode (BTN_SWAP off):
- O_CAPS_L1: Space (tap) / Space (hold) / Layer 1 (hold 200ms)
- I_SPC_L1: Caps (tap) / Space (hold) / Layer 1 (hold 200ms)
Swapped Mode (BTN_SWAP on):
- O_CAPS_L1: Caps (tap) / Space (hold) / Layer 1 (hold 200ms)
- I_SPC_L1: Space (tap) / Space (hold) / Layer 1 (hold 200ms)
RGB feedback: Trackball LEDs change to white/blue to indicate swap state Sync: Master automatically syncs swap state to slave via RPC
- F: Left Control (MOD_LCTL)
- G: Left Shift (MOD_LSFT)
- H: Right Shift (MOD_RSFT)
- J: Right Control (MOD_RCTL)
- Tapping term: 280ms (customized for home row)
| Combo | Keys | Output | Purpose |
|---|---|---|---|
| Left Parenthesis | E + W | ( | Quick bracket access |
| Right Parenthesis | I + O | ) | Quick bracket access |
| Left Brace | D + S | { | Quick bracket access |
| Right Brace | L + K | } | Quick bracket access |
| Left Bracket | C + X | [ | Quick bracket access |
| Right Bracket | . + , | ] | Quick bracket access |
| Delete | F + D | Del | Quick delete |
| Backspace | K + J | Bksp | Quick backspace |
| Left Middle Mouse | L_MB1 + L_MB2 | MB3 | Middle click (left trackball) |
| Right Middle Mouse | R_MB1 + R_MB2 | MB3 | Middle click (right trackball) |
| Mouse Layer Middle | ML_MB1 + ML_MB2 | MB3 | Middle click (mouse layer) |
| Refresh | LTB_BK + RTB_FW | F5 | Browser refresh |
| Caps Lock | Tab + Q | Caps | Quick caps lock |
| Auto Mouse Toggle | 5 + 6 | ML_AUTO | Toggle auto mouse layer |
| Equals | 0 + Backspace | = | Quick equals sign |
| Button Swap | Esc + 1 | B_SWAP | Toggle button swap |
| Ctrl+Delete | F + G | Ctrl+Del | Delete word forward |
| Ctrl+Backspace | H + J | Ctrl+Bksp | Delete word backward |
| Password Macro | B + Play | " 2326" | Custom password entry |
| Left Arrow | C + V | < | Shift+comma |
| Right Arrow | M + , | > | Shift+period |
- 32-bit timers: Caps lock timer uses
uint32_tto prevent overflow (30+ second timeout) - 16-bit timers: All other timers (safe for <60 second durations)
- Timer limiter: 500ms minimum between updates to reduce overhead
- Housekeeping rate: Runs every matrix scan (~1000Hz)
- Timeout: 30,000ms (30 seconds) of inactivity
- Overflow protection: Uses 32-bit timer and
timer_elapsed32()
- Polling rate: 100-1000Hz depending on trackball activity
- Layer caching: Single calculation per layer change (not per mouse report)
- Optimized lookups: Cached highest layer state for performance
- Home row mods: 280ms
- Layer toggle keys: 100ms (Play/Mute)
- Special layers: 0ms (instant activation for LT(4))
- Default: 200ms (TAPPING_TERM)
- ESC_GRV: Esc (tap) / Grave (hold)
- BSPC_MINS: Backspace (tap) / Minus (hold) / Minus with Shift
- R_MB1/R_MB2: Letters Y/U (tap) / Mouse buttons (on trackball movement)
- L_MB1/L_MB2: Letters T/R (tap) / Mouse buttons (on trackball movement)
- ML_MB1/ML_MB2: Mouse buttons (auto mouse layer)
- LTB_BK: Left Tab (tap) / Browser Back (hold)
- RTB_FW: Right Tab (tap) / Browser Forward (hold)
- CT_UN: Close Tab (tap) / Undo Close Tab (hold)
- CW_LW: Last Window (tap) / Cycle Windows (hold)
- DL_DR: Virtual Desktop Left (tap) / Right (hold)
- PD_PU: Page Down (tap) / Page Up (hold)
- HM_EN: Home (tap) / End (hold)
- NX_PR: Next Track (tap) / Previous Track (hold)
- VU_VD: Volume Up (tap) / Volume Down (hold)
- UN_RE: Undo (tap) / Redo (hold)
- CO_PA: Copy (tap) / Paste (hold)
- DE_CU: Delete (tap) / Cut (hold)
- CW_FS: Close Window (tap) / Fullscreen (hold)
- CT_TW: Cycle Taskbar (tap) / Alt+Tab Window Cycle (hold)
- B_SWAP: Toggle button swap mode
- ML_AUTO: Toggle auto mouse layer
- FX_SLV_M/P: Decrease/increase growth factor (÷2 / ×2)
- Message types:
- Layer sync (type 1): Updates slave trackball RGB on layer changes
- Button swap sync (type 2): Synchronizes BTN_SWAP state across halves
- Automatic sync: Master sends updates on every layer change
- Initialization: BTN_SWAP state synced on keyboard boot
- Safety checks: Master-only transmission, slave-only reception
- Target modes:
- Slave only (0): Updates only slave trackball
- Master only (1): Updates only master trackball
- Both (2): Updates both trackballs simultaneously
- Conditional updates: Only sends RPC when necessary to reduce I2C traffic
- Master/slave checks: All split-specific code protected by
is_keyboard_master() - Bounds validation: Layer values validated before RPC transmission
- Overflow protection: 32-bit timers for long-duration timeouts
- Race condition prevention: Mutual exclusion with
else ifin housekeeping - Timer consistency: Cached timer values within single events
- Mode state validation: Enum-based mode system prevents invalid states
- Static allocations: All state variables pre-allocated (no malloc)
- Cached layer state: Single calculation stored for repeated use
- Minimal RPC overhead: 2-byte messages for synchronization
- Efficient timers: 16-bit timers where sufficient, 32-bit only when needed
# Left half
make lily58/rev1:via:flash -e POINTING_DEVICE=trackball_trackball -e POINTING_DEVICE_POSITION=left -j 8
# Right half
make lily58/rev1:via:flash -e POINTING_DEVICE=trackball_trackball -e POINTING_DEVICE_POSITION=right -j 8#define TAPPING_TERM 200
#define TAPPING_TERM_PER_KEY
#define SPLIT_TRANSACTION_IDS_USER USER_SYNC
#define DOUBLE_TAP_SHIFT_TURNS_ON_CAPS_WORD // Optional
#define BOTH_SHIFTS_TURNS_ON_CAPS_WORD // OptionalCOMBO_ENABLE = yes
CAPS_WORD_ENABLE = yes
SEND_STRING_ENABLE = yes
MOUSEKEY_ENABLE = yes
POINTING_DEVICE_ENABLE = yes- Layer caching optimization: ~0.14% CPU saved (~184,000 cycles/sec)
- Mouse processing: <1% CPU during active trackball use
- Idle overhead: Minimal (<0.1% CPU)
- Key response: <5ms (typical QMK matrix scan)
- Mouse reports: 1-10ms depending on polling rate
- Layer changes: Instant (cached state)
- RGB updates: <2ms per update
- Maximum layers: 5 (0-4)
- Timer overflow protection: Up to 49.7 days (32-bit timers)
- Growth factor range: 0.25x to theoretically unlimited (practical max ~64x)
- Combo term: 50ms default (configurable per combo)
- Double-tap window: 400ms for mode switching
- Mouse Sensitivity Adjustment: Use FX_SLV_M/P keys to halve or double the cursor acceleration on-the-fly
- Arrow Key Navigation: Double-tap trackball button for precise text editing without leaving home position
- Scrolling: Hold trackball button for 400ms to activate smooth scrolling
- Button Swap: Toggle with combo (Esc+1) to switch between typing and mousing workflows
- Auto Mouse Layer: Enable for automatic layer switching when moving trackball
- Home Row Mods: Slight delay (280ms) allows normal typing without accidental modifiers
All constants are defined at the top of the keymap for easy modification:
LAYER_CHANGE_DELAY: 200msTIMER_LIMITER: 500msATML_TIMEOUT: 1500msRGB_MS_TIMEOUT: 1500msARROW_MOMENTUM: 0.99ARROW_STEP: 6 pixelsSCROLL_DIVISOR_H/V: 8.0GROWTH_FACTOR: 8.0 (adjustable at runtime)MOMENTUM: 0.06MIN_SCALE: 0.0001MAX_SCALE: 64.0
Based on QMK Firmware with extensive customization for dual trackball integration and advanced mouse emulation.
License: GPL v2 (inherited from original Lily58 keymap by Naoki Katahira)