Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,508 changes: 891 additions & 617 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rmkit"
version = "0.0.21"
version = "0.1.0"
edition = "2021"
homepage = "https://github.com/rmk-rs/rmkit"
repository = "https://github.com/rmk-rs/rmkit"
Expand All @@ -9,7 +9,11 @@ description = "rmkit is a toolkit set for RMK keyboard firmware"
license = "Apache-2.0"

[dependencies]
rmk-config = { version = "0.6.0" }
# TODO: back to crates.io versions (rmk-config = "0.7", rynk-kle) once they are
# published — a git dependency cannot be published.
rmk-config = { git = "https://github.com/rmk-rs/rmk", rev = "93726512f2fb383cdb5a419b09ff181c7c123b5f" }
# The KLE/Vial ↔ [layout] conversion engine; `rmkit layout` is CLI glue over it.
rynk-kle = { git = "https://github.com/rmk-rs/rmk", rev = "93726512f2fb383cdb5a419b09ff181c7c123b5f" }
clap = { version = "4.5.23", features = ["derive", "string"] }
toml = "0.9.8"
serde = "1.0"
Expand Down
40 changes: 38 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ Now rmkit can be used to generate RMK project directly from `keyboard.toml` and

```shell
# macOS/linux
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/rmk-rs/rmkit/releases/download/v0.0.1/rmkit-installer.sh | sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/rmk-rs/rmkit/releases/latest/download/rmkit-installer.sh | sh

# Windows(powershell)
powershell -ExecutionPolicy ByPass -c "irm https://github.com/rmk-rs/rmkit/releases/download/v0.0.1/rmkit-installer.ps1 | iex"
powershell -ExecutionPolicy ByPass -c "irm https://github.com/rmk-rs/rmkit/releases/latest/download/rmkit-installer.ps1 | iex"
```

2. Create RMK project from `keyboard.toml` and `vial.json`:
Expand All @@ -40,3 +40,39 @@ Now rmkit can be used to generate RMK project directly from `keyboard.toml` and
```

The available project template can be found at [rmk-template](https://github.com/rmk-rs/rmk-template)

## Layout tools

`rmkit layout` works with the physical `[layout]` section of a `keyboard.toml`. The conversion engine is the [`rynk-kle`](https://github.com/rmk-rs/rmk/tree/main/rynk/rynk-kle) library crate (which also compiles to a wasm package for the web); this CLI wraps it.

Convert a [KLE](http://www.keyboard-layout-editor.com/) JSON export or a [Vial](https://get.vial.today/) definition (`vial.json`) into RMK's `[layout]` — key positions, cap sizes, split gaps, rotation, ISO/L-shaped caps, encoders, and VIA layout options are all converted, and the result is validated against RMK's own layout builder. KLE carries no keycodes, so author the `[keymap]` yourself:

```shell
rmkit layout convert path/to/vial.json -o layout.toml # vial.json → [layout]
rmkit layout convert path/to/kle_export.json # raw KLE "Download JSON" export too
rmkit layout convert --to-vial keyboard.toml # reverse: [layout] → vial.json
```

Render a physical layout as box-drawing art, to check the geometry in a terminal without flashing anything. The input can be a `keyboard.toml`, or a `vial.json` / raw KLE export directly (converted on the fly); `--variant` picks one `[[layout.variant]]`:

```shell
$ rmkit layout show keyboard.toml
variant 'default': 17 keys, 4u × 5u

┌─────┬─────┬─────┬─────┐
│ 0,0 │ 0,1 │ 0,2 │ 0,3 │
│ │ │ │ │
├─────┼─────┼─────┼─────┤
│ 1,0 │ 1,1 │ 1,2 │ │
│ │ │ │ │
├─────┼─────┼─────┤ 1,3 │
│ 2,0 │ 2,1 │ 2,2 │ │
│ │ │ │ │
├─────┼─────┼─────┼─────┤
│ 3,0 │ 3,1 │ 3,2 │ │
│ │ │ │ │
├─────┴─────┼─────┤ 3,3 │
│ 4,0 │ 4,1 │ │
│ │ │ │
└───────────┴─────┴─────┘
```
61 changes: 31 additions & 30 deletions keyboard.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,38 @@ product_name = "RMK Keyboard"
vendor_id = 0x4c4b
product_id = 0x4643
manufacturer = "haobo"
chip = "rp2040"
chip = "nrf52840"

[layout]
# WARNING: Currently row2col/col2row is set in RMK's feature gate, configs here do nothing actually
# row2col = true
rows = 4
cols = 3
map = """
(0,0) (0,1) (0,2)
(1,0) (1,1) (1,2)
(2,0) (2,1) (2,2)
(3,0) (3,1) (3,2)
"""

[keymap]
layers = 2
keymap = [
[
["A", "B", "C"],
["Kc1", "Kc2", "Kc3"],
["LCtrl", "MO(1)", "LShift"],
["OSL(1)", "LT(2, Kc9)", "LM(1, LShift | LGui)"]
],
[
["_", "TT(1)", "TG(2)"],
["_", "_", "_"],
["_", "_", "_"],
["_", "_", "_"]
],
]

[[keymap.layer]]
name = "base"
keys = """
A B C
Kc1 Kc2 Kc3
LCtrl MO(1) LShift
OSL(1) LT(2, Kc9) LM(1, LShift | LGui)
"""

[[keymap.layer]]
name = "fn"
keys = """
_ TT(1) TG(2)
_ _ _
_ _ _
_ _ _
"""

[storage]

Expand All @@ -42,8 +52,8 @@ row_offset = 0
col_offset = 0
ble_addr = [0x18, 0xe2, 0x21, 0x80, 0xc0, 0xc7]
[split.central.matrix]
input_pins = ["P0_12", "P0_13"]
output_pins = ["P0_14", "P0_15"]
row_pins = ["P0_12", "P0_13"]
col_pins = ["P0_14", "P0_15"]

[[split.peripheral]]
rows = 2
Expand All @@ -52,14 +62,5 @@ row_offset = 2
col_offset = 2
ble_addr = [0x7e, 0xfe, 0x73, 0x9e, 0x66, 0xe3]
[split.peripheral.matrix]
input_pins = ["P1_11", "P1_10"]
output_pins = ["P0_30"]

# [[split.peripheral]]
# rows = 2
# cols = 1
# row_offset = 2
# col_offset = 2
# serial = [{ instance = "UART0", tx_pin = "PIN_0", rx_pin = "PIN_1" }]
# input_pins = ["PIN_9", "PIN_11"]
# output_pins = ["PIN_10"]
row_pins = ["P1_11", "P1_10"]
col_pins = ["P0_30"]
35 changes: 35 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,39 @@ pub enum Commands {
#[arg(long)]
keyboard_toml_path: String,
},
/// Physical-layout tools: convert KLE/Vial layouts and preview [layout] geometry
Layout {
#[command(subcommand)]
command: LayoutCommands,
},
}

#[derive(Subcommand, Debug)]
pub enum LayoutCommands {
/// Convert a KLE JSON export or vial.json into an RMK [layout] section (or back with --to-vial)
Convert {
/// Path to a KLE JSON export or a vial.json (a keyboard.toml with --to-vial)
input: String,

/// Write the result to FILE instead of stdout
#[arg(short, long)]
output: Option<String>,

/// Reverse: a keyboard.toml's [layout] → a minimal vial.json
#[arg(long)]
to_vial: bool,

/// Skip the rmk-config round-trip check (forward only)
#[arg(long)]
no_validate: bool,
},
/// Render a physical layout (keyboard.toml, vial.json, or KLE export) as box-drawing art
Show {
/// Path to a keyboard.toml (or bare [layout] snippet), a vial.json, or a raw KLE JSON export
input: String,

/// Render only this [[layout.variant]] (default: all)
#[arg(long)]
variant: Option<String>,
},
}
2 changes: 2 additions & 0 deletions src/chip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub(crate) fn get_chip_options(split: bool) -> Vec<&'static str> {
"Pi Pico W",
"esp32c3",
"esp32c6",
"esp32h2",
"esp32s3",
]
} else {
Expand All @@ -41,6 +42,7 @@ pub(crate) fn get_chip_options(split: bool) -> Vec<&'static str> {
"esp32c3",
"esp32s3",
"esp32c6",
"esp32h2",
"nice!nano_v2",
"XIAO BLE",
"nice!nano",
Expand Down
42 changes: 16 additions & 26 deletions src/keyboard_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@ pub(crate) fn parse_keyboard_toml(
target_dir: Option<String>,
) -> Result<ProjectInfo, Box<dyn std::error::Error>> {
let keyboard_toml_config = KeyboardTomlConfig::new_from_toml_path(keyboard_toml);
let hardware = keyboard_toml_config.hardware()?;
let host = keyboard_toml_config.host();

let project_name = keyboard_toml_config
.get_device_config()
.name
.replace(" ", "_");
let project_name = keyboard_toml_config.identity()?.name.replace(" ", "_");
let target_dir = if let Some(dir) = target_dir {
dir
} else {
Expand All @@ -48,39 +47,30 @@ pub(crate) fn parse_keyboard_toml(

// Check keyboard.toml

// Storage config
let storage_config = keyboard_toml_config.get_storage_config();
if !storage_config.enabled {
if hardware.storage.is_none() {
disabled_default_feature.push("storage".to_string());
}

// Defmt config
let dep_config = keyboard_toml_config.get_dependency_config();
if !dep_config.defmt_log {
if !hardware.dependency.defmt_log {
disabled_default_feature.push("defmt".to_string());
}

if !keyboard_toml_config.get_host_config().vial_enabled {
// Vial and Rynk are mutually exclusive, and `vial` is a default feature.
if host.rynk_enabled {
disabled_default_feature.push("vial".to_string());
disabled_default_feature.push("vial_lock".to_string());
}

// Light config requires controller feature if any light pin is configured
let light_config = keyboard_toml_config.get_light_config();
if light_config.capslock.is_some()
|| light_config.scrolllock.is_some()
|| light_config.numslock.is_some()
{
enabled_feature.push("controller".to_string());
enabled_feature.push("rynk".to_string());
} else if !host.vial_enabled {
// No host protocol at all, so the lock gate has nothing to guard either.
disabled_default_feature.push("vial".to_string());
disabled_default_feature.push("host_lock".to_string());
}

let board_config = keyboard_toml_config.get_board_config().unwrap();
let matrix_type = match board_config {
rmk_config::BoardConfig::Split(_) => "split".to_string(),
rmk_config::BoardConfig::UniBody(_) => "normal".to_string(),
let matrix_type = match hardware.board {
rmk_config::resolved::hardware::BoardConfig::Split(_) => "split".to_string(),
rmk_config::resolved::hardware::BoardConfig::UniBody(_) => "normal".to_string(),
};

let chip_model = keyboard_toml_config.get_chip_model().unwrap();
let chip_model = hardware.chip;
let chip_or_board = if let Some(board) = chip_model.board {
board
} else {
Expand Down
Loading
Loading