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, a full 5-layer layout, and ZMK Studio support 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 |
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 , . %
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 v1.5.0 (local build). The left half is the tightest because ZMK Studio ships on it.
| Build | Flash used | Flash free | RAM used | RAM free |
|---|---|---|---|---|
| Left (Studio + nice_view) | 376 KB / 792 KB (47.5%) | 416 KB | 96 KB / 256 KB (37.5%) | 160 KB |
| Right (nice_view) | 269 KB / 792 KB (33.9%) | 523 KB | 61 KB / 256 KB (24.0%) | 195 KB |
| Settings reset | 45 KB / 792 KB (5.7%) | — | 11 KB / 256 KB (4.4%) | — |
Over 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. Dropping ZMK 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 (ZMK 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.
With the Studio firmware on the left half, you can edit keymaps live without rebuilding:
- Connect the left half via USB.
- Open studio.zmk.dev in Chrome/Edge (WebSerial required).
- Changes apply immediately; click Save to persist to flash.
Rebuilding and reflashing will overwrite Studio changes — export your keymap first if needed.