Warning
Personal configuration — use at your own risk. Releases are experimental and built for one specific keyboard. Key layout, timing, and layer assignments are tuned to personal preference and may change without notice. If you have the same hardware, feel free to use this as a starting point, but do not expect a stable or general-purpose firmware.
Original hardware by 380465425@qq.com · Original README (Chinese): README_JP.md
ZMK firmware configuration for the Eyelash Sofle split keyboard (NRF52840, nice!nano v2, nice!view display). Features home row mods, an 8-layer layout (including gaming layers), and DYA Studio support — live keymap editing over USB and Bluetooth — on the left half.
See KEYMAP.md for the full layer reference and hold-tap timing documentation.
See WM_KEYBINDS.md for suggested window manager keybind configuration (niri, GNOME, COSMIC) to match the NAV layer workspace and monitor navigation keys.
| # | Name | Access |
|---|---|---|
| 0 | BASE | always on |
| 1 | NAV | hold mo1 — left thumb |
| 2 | CODE | hold lt(2,DEL) — right thumb |
| 3 | MEDIA | hold mo3 — right thumb |
| 4 | SYS|NUM | hold mo4 — left thumb |
| 5 | GAME? (picker) | &to 5 — SYS right-thumb outer key |
| 6 | MINECRFT | picker joystick ↑ |
| 7 | GAME (generic) | picker joystick ↓ |
QWERTY with home row mods and AltGr mod-tap:
| Key | Tap | Hold |
|---|---|---|
| S | S | LALT |
| D | D | LCTRL |
| F | F | LSHFT |
| J | J | RSHFT |
| K | K | RCTRL |
| L | L | LALT |
| F12 (right thumb) | F12 | RALT (AltGr) |
Left thumb: mo(SYS|NUM) · F11 · LGUI · mo(NAV) · BSPC
Right thumb: SPACE · lt(CODE,DEL) · CapsWord · F12 / RALT · mo(MEDIA)
Central joystick column (top→bottom): Vol↑ · Vol↓ · Play/Pause · Next · Mute — direct media access without holding mo(MEDIA).
Hold-tap timing: tapping-term=280ms · require-prior-idle=230ms · quick-tap=175ms · flavor=balanced
- Top row: F12 · F1–F11
- Row 2: Home · PgDn · PgUp · End · Ins · Print (mouse buttons removed — see SYS|NUM)
- Home row: WM monitor/workspace navigation (Super+key combos) · Left · Down · Up · Right (HJKL) · Del
- Bottom row: WM move-to-workspace/monitor combos · workspace shortcuts (ws1 · next · prev · last) · WM mon-left · WM mon-right
- Central joystick column (top→bottom): ↑ · ↓ · ← · → · Enter
- Thumb: Del · App menu · LCTRL · Left · Right
Left home + bottom rows, right side transparent.
Home row: ` · { · }(LALT) · [(LCTRL) · ](LSHFT) · +
Bottom row: - · _ · ( · ) · =
Home row: Vol↓ · Vol↑ · Play/Pause · Stop
Bottom row: F20 (mic mute) · Mute · Prev · Next
Central joystick column mirrors the BASE central column (Vol↑/Vol↓/Play-Pause/Next/Mute) — kept duplicated for clarity.
Left — Bluetooth: CLR · BT0–BT4
Left — RGB: Brightness · Saturation · Hue · Effect (increase row 1 / decrease row 2) · RGB Toggle
Mouse — emergency fallback (only on this layer; NAV no longer carries mouse keys):
| Key | Action |
|---|---|
R / T |
Mouse 4 / Mouse 5 (back / forward) |
F / G / V |
Mouse 1 / 2 / 3 (left / right / middle click) |
| Central joystick column | Pointer move (↑/↓/←/→) |
| Central joystick (row 4) | Left click |
Right — Numpad (aligned with base layer 7/8/9):
NUM 7 8 9 -
/ 4 5 6 +
* 1 2 3 Ent
= 0 , . %
Locked gaming layers that bypass the home-row-mod / hold-tap machinery entirely, so WASD, SPACE and the modifiers behave as pure keys. Entered explicitly and held by a toggle — no key needs to stay pressed. (See issue #11 for the design.)
Enter: hold mo(SYS|NUM) (left thumb) → tap the right-thumb outer key (&to 5) → the GAME? picker opens. Then push the joystick to choose:
| Joystick | Goes to | OLED | Underglow |
|---|---|---|---|
| ↑ | MINECRFT (layer 6) | MINECRFT |
green |
| ↓ | GAME generic FPS/MOBA (layer 7) | GAME |
blue |
| center | cancel → BASE |
Every other key on the picker is &none, so a stray press can't fall through to BASE.
Inside a game layer:
- WASD/QWER and the right half are plain
&kp(chat/console work normally) — zero relearning, just no HRM. - LCTRL on the bottom-left pinky (replaces
\), LSHIFT on the left thumb, SPACE on both thumbs, BSPC + LALT on the right thumb. - F13 on the right thumb = push-to-talk (bind it in Discord/OBS).
mo(MEDIA)is still reachable. - Joystick = audio cluster (Vol↑ / Vol↓ / Mute / Play-Pause) + center Enter. Encoder = volume.
- MINECRFT has
F3(debug) top-left; the generic GAME layer has`(console) there.
Exit / switch:
- Exit → BASE: press the two top corners together (positions 0 + 12) —
GAME_EXITturns the underglow off and returns to BASE. - Re-pick: press top-left two keys together (positions 0 + 5) to reopen the picker without bouncing through BASE.
RGB caveat: the per-game underglow colour only shows on battery/BLE.
CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB=yturns underglow off on USB power (typical wired desktop gaming), so the OLED name (MINECRFT/GAME) is the primary active-mode indicator. SetAUTO_OFF_USB=nif you want the colour while wired.
One command downloads the latest firmware from GitHub Actions and walks you through flashing both halves:
mise run flash-releaseFollow the on-screen prompts: double-tap reset on the left half when asked, then on the right half. The task handles everything else (artifact download, mount detection, sync, unmount wait).
Requires mise and gh (GitHub CLI, authenticated). If you don't have them, see FLASHING_MANUAL.md for the drag-and-drop flow.
If you've used ZMK Studio to edit the keymap live, the changes are stored on the left half's settings partition and survive a normal firmware flash. Symptom: you flash a new release and the keymap still behaves like the old version.
Fix — flash settings reset first, then the normal firmware on top:
mise run flash-release-reset # double-tap the LEFT half when prompted
mise run flash-release # double-tap left, then rightThis also wipes the left half's Bluetooth bonds — see Re-pairing the halves below if the halves don't auto-pair afterwards.
For reference — the mise task downloads these automatically, but if you're flashing manually they are:
| File | Target |
|---|---|
eyelash_sofle_studio_left.uf2 |
Left half (ZMK Studio enabled) |
nice_view-eyelash_sofle_right-zmk.uf2 |
Right half |
settings_reset-nice_nano_v2-zmk.uf2 |
Either half — clears Studio overlay + BT bonds |
For the drag-and-drop flashing flow without mise, see FLASHING_MANUAL.md.
If the halves fail to connect to each other, clear their Bluetooth bonds and let them re-discover:
Left half — via the keymap (no reflash needed):
- Hold
mo(SYS|NUM)(left thumb) and tapBT_CLR(top-left of the SYS layer, aboveQ).
Right half — always requires settings reset:
- Flash
settings_reset-nice_nano_v2-zmk.uf2to the right half (the right half has no independent access to the SYS layer when disconnected).
After clearing bonds:
- Power off both halves.
- Power on the right half first (peripheral — starts advertising).
- Power on the left half (central — scans and connects).
- They auto-pair within a few seconds.
Headroom available for future keymap growth, measured on the feat/rgb-layer-module branch (DYA Studio: cormoran ZMK fork + 5 modules, plus the in-repo zmk-rgb-layer underglow module). The left half is the tightest because Studio + the DYA modules ship on it (USB CDC-ACM + BLE GATT studio transports both compiled in); zmk-rgb-layer compiles central-only, so it adds to the left half only.
| Build | Flash used | Flash free | RAM used | RAM free |
|---|---|---|---|---|
| Left (Studio + nice_view) | 409 KB / 792 KB (51.6%) | 383 KB | 107 KB / 256 KB (41.9%) | 149 KB |
| Right (nice_view) | 270 KB / 792 KB (34.1%) | 522 KB | 62 KB / 256 KB (24.3%) | 194 KB |
| Settings reset | 45 KB / 792 KB (5.7%) | — | 11 KB / 256 KB (4.4%) | — |
Just under half the flash is still free on the left half and two-thirds on the right, so new layers, macros, and combos have plenty of room. The DYA Studio modules added ~21 KB to the left half; the zmk-rgb-layer indicator module is negligible (well under 1 KB). Dropping Studio from the left half would recover a large chunk of flash if a future feature ever needs it.
Refresh the numbers after any significant keymap change by re-reading the Memory region block in the latest Build ZMK firmware run log.
This repo uses ZMK Firmware with a west workspace and mise for environment management.
sudo pacman -S cmake ninja dtc git dfu-util
yay -S python-west miseWhy not
yay -S zephyr-sdk? The AUR package installs SDK 1.0.0 (March 2026), which requires Zephyr 4.2+. ZMK v0.3.0 targets Zephyr 3.5 — incompatible.
The SDK goes into tools/ (already gitignored):
mkdir -p tools
wget -P tools https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.17.0/zephyr-sdk-0.17.0_linux-x86_64_minimal.tar.xz
tar xf tools/zephyr-sdk-0.17.0_linux-x86_64_minimal.tar.xz -C tools/
tools/zephyr-sdk-0.17.0/setup.sh -t arm-zephyr-eabi -h -c
rm tools/zephyr-sdk-0.17.0_linux-x86_64_minimal.tar.xzmise provisions Python 3.12 via uv and auto-activates a .venv on cd:
mise install
mise run setupsetup runs west init, west update, west zephyr-export, and installs Python deps into the venv. ZMK source lands in zmk/ and modules/ (gitignored).
Build the firmware locally:
mise run build-left # left half (DYA Studio enabled)
mise run build-right # right half
mise run build-reset # settings reset (clears Studio overlay + BT bonds)Flash the locally-built firmware:
mise run flash-left # or flash-right / flash-resetEach flash-* task prompts you to double-tap reset, waits up to 60 seconds for the NICENANO drive to mount, copies the .uf2, and syncs the write. The keyboard reboots automatically once the copy completes.
For a one-shot rebuild + interactive flash of both halves with a memory-budget summary at the end:
mise run build-flashIf you just want to flash a release without building from source, use
mise run flash-releaseinstead — it downloads the latest main artifacts from GitHub Actions. See the Flashing section above.
The keymap-drawer SVG can be wrapped into a printable A4 PDF (header with version + footer with date and repo URL). Three sources are available depending on what you want to print:
mise run draw-pdf # use the SVG already in keymap-drawer/ (no regen, no fetch)
mise run draw-pdf-fresh # regenerate the SVG locally first (mise run draw), then build the PDF
mise run draw-pdf-ci # fetch the CI-rendered SVG from origin/main, then build the PDF
mise run open-pdf # generate from local SVG + open in default viewer (xdg-open)The PDF is written to keymap-drawer/eyelash_sofle.pdf (gitignored — rebuild on demand). weasyprint is provisioned by mise via the pipx:weasyprint entry in [tools]; if it's missing, run mise install. To upgrade: mise upgrade pipx:weasyprint.
keymap-editor is a browser-based visual keymap editor that commits changes directly to this repo and triggers a firmware build automatically.
Setup (one-time):
- Install the keymap-editor GitHub App and grant it access to this repo.
- Open nickcoutsos.github.io/keymap-editor and sign in with GitHub.
- Select this repo — it will detect
config/eyelash_sofle.keymapandconfig/eyelash_sofle.jsonautomatically.
Workflow:
- Edit keybindings visually in the browser.
- Click Save — keymap-editor commits the updated
.keymaptomain. - GitHub Actions runs automatically:
build.ymlbuilds new firmware and uploads.uf2artifacts.draw.ymlregenerates the keymap SVG inkeymap-drawer/.
- Download the artifacts from the Actions tab and flash.
The
build.ymlanddraw.ymlworkflows trigger onmainpushes only. Useworkflow_dispatchfrom the Actions tab to build manually from any branch.
The left half ships cormoran's ZMK-Studio fork firmware, so you can edit the keymap live — over USB or Bluetooth — without rebuilding:
- Open a compatible ZMK-Studio web app in a Chromium browser (Chrome/Edge):
- Sofle Studio — a
cormoran/dya-studiofork tailored for this keyboard, maintained as a separate project (run it from there). (work in progress) - or the upstream hosted app: studio.dya.cormoran.works
- Sofle Studio — a
- Connect the left half over USB (WebSerial) or Bluetooth (WebBluetooth).
- Changes apply immediately; click Save to persist to flash. The encoder and BLE profiles are configurable here too.
Locking is disabled, so no unlock step is needed. Rebuilding/reflashing overwrites Studio changes — export first if needed. The studio web app is a separate project — this repo only provides the firmware that talks to it.