Rynk
Rynk is RMK's own protocol for configuring a keyboard while it's running — change your keymap, layers, combos, tap-dance, macros and more without reflashing the firmware. It plays the same role as Vial, but it is built natively for RMK and understands every RMK feature.
Rynk speaks RMK's full feature set and is enabled by default, but its host tools are still young: today you use it from a browser demo or a small Rust library — there is no packaged desktop app yet. Vial has a polished cross-platform app, but only supports the features in the Vial standard.
If you want a click-and-go app today, choose Vial. If you want access to every RMK feature and don't mind newer tooling, choose Rynk.
What you can configure
Through Rynk, a host tool can read and change:
- Keymap entries per layer, plus encoders and the default layer
- Combos, forks, tap-dance / morse keys, and macros
- Tap-hold and other behavior settings
It can also watch live status — the current layer, a key tester (matrix state), typing speed (WPM), the caps-lock/num-lock indicators, battery level, and, on wireless boards, the connection and BLE profile (including switching or clearing a profile). Finally it can reboot the keyboard, enter the bootloader, and reset stored settings.
Enable Rynk
Rynk and Vial are mutually exclusive — pick one. For keyboard.toml projects,
select Rynk in [host]:
Then make sure the rmk dependency enables the rynk feature. It's on by
default, so keeping RMK's default features is enough:
If you turn default features off, add the ones you need back explicitly:
The [host] setting and the Cargo feature must agree. For keyboard.toml
projects RMK checks this when it builds: if the rynk feature is on,
rynk_enabled must be true (and vial_enabled false), otherwise the build
stops with an error. To use Vial instead, flip both settings and swap the rynk
feature for vial.
Keep the storage feature enabled (it's on by default) so your changes survive
a reboot. Without it, anything you set over Rynk is lost when the keyboard
restarts. See Storage.
Connecting to your keyboard
Rynk works over the connection you already use:
- USB — plug the keyboard in. Host tools find RMK keyboards automatically, so you don't have to hunt for the right serial port.
- Bluetooth — native tools connect to an already-connected keyboard (the OS must have it bonded and currently connected).
- Browser — Chromium browsers (Chrome or Edge) connect over USB (Web Serial) or Bluetooth (WebHID). Firefox and Safari are not supported.
Rynk tooling is still young. Today you have two options:
- A browser demo — the
rynk-wasmpackage ships a reference web page (index.html) you build and serve locally; follow the steps in its README. - Rust libraries — the
rynk,rynk-serial, andrynk-blecrates let you build a native tool (see For tool authors below). The browser build is a separate package,rynk-wasm.
A ready-made desktop app is not available yet.
Locking dangerous operations
By default Rynk locks the operations that could plant a persistent implant or leak what you type, so a background process — or a web page you granted access to once — can't quietly reflash your keyboard or read your keystrokes. To use a locked operation you prove you're physically present by holding a key combo.
There are three tiers:
- Open — everything you normally reach for: read the keymap, change keys, layers, combos, macros, switch BLE profiles, reboot. These stay available so on-the-fly configuration is friction-free.
- Locked — the dangerous ones, always gated: entering the bootloader, resetting stored settings and bonds, reading the live key matrix (a keylogger if left open), and clearing a BLE bond. A host tool gets a "locked" error until you unlock.
- Config writes — open by default, because on-the-fly configuration is the
point. Set
write_requires_unlock = trueto move every write (keymap, macros, …) into the locked tier as well.
Unlocking
Set the keys the owner holds to unlock in [host].unlock_keys (up to four):
When you trigger a locked action, the host tool reads the challenge and shows "hold these keys". Hold them together; the tool polls briefly until the device unlocks, and the session stays unlocked until it ends. Unplugging, a Bluetooth disconnect, or an explicit lock re-locks the device.
If you leave unlock_keys unset, the locked operations can never be unlocked —
a safe default, but it means a fresh config can't enter the bootloader over Rynk
or use the matrix tester until you add the keys.
For local development you can bypass the gate entirely:
Config reads stay open, so anything that can reach the protocol can read your stored macros. Don't put passwords or other secrets in a macro.
unlock_keys
[host].unlock_keys guards this Rynk lock gate. A separate [dfu].unlock_keys
guards the firmware download once the device is already in the bootloader (the
dfu_lock feature). They're independent — set either, both, or neither. A setup
that cares most about firmware replacement wants both: the Rynk gate blocks the
remote route into the bootloader, and dfu_lock covers a physically-present
attacker who reaches it another way.
Advanced tuning
Rynk's firmware buffers size themselves automatically and rarely need touching.
There are several parameters in keyboard.toml's [rmk] section that you can adjust:
rynk_buffer_size: the buffer size used for encoding/decoding Rynk message. A larger buffer moves more per round-trip at the cost of RAM.protocol_macro_chunk_size: macro chunk size.
The optional bulk Cargo feature turns on faster bulk transfers. The bulk size is limited by rynk_buffer_size. Enable it on boards that have room to spare:
Host tools work with or without bulk — the keyboard tells the tool whether it
supports bulk transfers, so the same tool works either way.
For tool authors
If you're building your own host tool, RMK ships ready-made client crates so you don't have to implement the protocol yourself:
rynk— the core typed client; talks to a keyboard over any byte link.rynk-serial— USB discovery and connection.rynk-ble— native Bluetooth discovery and connection.rynk-wasm— the browser build, driven from JavaScript over Web Serial / WebHID.
A minimal native USB tool looks like this:
Live status arrives as "topics" you poll for:
Topics are best-effort. If you miss one, just read the current value again with
the matching get_* method.
Firmware and host share the same message types (via rmk-types), so the two ends
can never disagree about a message's format. The client crates handle all the
encoding for you, so you never touch the raw bytes on the wire.