Smart Keymap Features

Smart Keys

Below are the smart key features implemented for smart keymap:

HID Keyboard Key

Keymap with a keyboard Key

Given a keymap.ncl:

let K = import "keys.ncl" in
{ keys = [ K.A ] }

When the keymap registers the following input

[
  press K.A,
]

Then the HID keyboard report should equal

{ key_codes = [K.A] }

Keymap with a modified keyboard Key

In keymap.ncl, modifiers can be merged with keys to form a modified key.

Given a keymap.ncl:

let K = import "keys.ncl" in
{ keys = [ K.A & K.LeftCtrl ] }

When the keymap registers the following input

[
  press (K.A & K.LeftCtrl),
]

Then the HID keyboard report should equal

{ modifiers = { left_ctrl = true }, key_codes = [K.A] }

TapHold Key

The TapHold key can behave differently depending on whether the key has been tapped or held.

e.g.

For examples of this key in other smart keyboard firmware, see e.g.:

Demonstrative Keymap

Let’s use a keymap with a tap-hold key, and a keyboard key.

Given a keymap.ncl:

let K = import "keys.ncl" in
{
  keys = [
    K.A & K.hold K.LeftCtrl,
    K.B
  ]
}

acts as ‘tap’ when tapped

When the keymap registers the following input

[
  press (K.A & K.hold K.LeftCtrl),
  release (K.A & K.hold K.LeftCtrl),
]

Then the HID keyboard report should equal

{ key_codes = [K.A] }

acts as ‘hold’ when held

When the keymap registers the following input

[
  press (K.A & K.hold K.LeftCtrl),
]

And the keymap ticks 500 times

Then the HID keyboard report should equal

{ modifiers = { left_ctrl = true } }

TapHold Key (configure resolve-as-hold timeout)

The “timeout” before a tap-hold is considered as held can be configured by the field config.tap_hold.timeout in keymap.ncl.

timeout configured to a low value

Given a keymap.ncl:

let K = import "keys.ncl" in
{
  config.tap_hold.timeout = 50,
  keys = [ K.A & K.hold K.LeftCtrl ],
}

When the keymap registers the following input

[
  press (K.A & K.hold K.LeftCtrl),
]

And the keymap ticks 60 times

Then the HID keyboard report should equal

{ modifiers = { left_ctrl = true } }

timeout configured to a high value

e.g. by setting the timeout to a very high value, the key still won’t resolve as “held” until that the timeout has been reached.

Given a keymap.ncl:

let K = import "keys.ncl" in
{
  config.tap_hold.timeout = 30000,
  keys = [ K.A & K.hold K.LeftCtrl ],
}

When the keymap registers the following input

[
  press (K.A & K.hold K.LeftCtrl),
]

And the keymap ticks 10000 times

Then the HID keyboard report should equal

{ modifiers = {}, key_codes = [] }

TapHold Key (configure interrupt response: ignore)

The tap hold key’s response to interruptions can be configured.

“Interrupts ignored” can be configured by setting config.tap_hold.interrupt_response to "Ignore" in keymap.ncl.

“Ignore interrupts” just means the key only acts as hold by holding the key for longer than the tap-hold timeout duration.

Demonstrative Keymap

Let’s demonstrate tap-hold “ignore interrupt” behaviour using a keymap with a tap-hold key, and a keyboard key:

Given a keymap.ncl:

let K = import "keys.ncl" in
{
  config.tap_hold.interrupt_response = "Ignore",
  keys = [
    K.A & K.hold K.LeftCtrl,
    K.B
  ]
}

rolling key presses (press TH(A), press B, release TH(A), release B)

Rolling the tap-hold key with another key (i.e. interrupting the tap-hold key with another key press), releasing the tap-hold key with interrupt_response = "Ignore" resolves the key as tap.

When the keymap registers the following input

[
  press (K.A & K.hold K.LeftCtrl),
  press (K.B),
  release (K.A & K.hold K.LeftCtrl),
  release (K.B),
]

Then the output should be equivalent to output from

[
  press (K.A),
  press (K.B),
  release (K.A),
  release (K.B),
]

interrupting tap (press TH(A), press B, release B, release TH(A))

After interrupting the tap-hold key with another key tap (press & release), releasing the tap-hold key with interrupt_response = "Ignore" resolves the key as tap.

When the keymap registers the following input

[
  press (K.A & K.hold K.LeftCtrl),
  press (K.B),
  release (K.B),
  release (K.A & K.hold K.LeftCtrl),
]

Then the output should be equivalent to output from

[
  press (K.A),
  press (K.B),
  release (K.B),
  release (K.A),
]

TapHold Key (configure interrupt response: hold on key press)

The tap hold key’s response to interruptions can be configured.

“Resolves as ‘Hold’ when interrupted by key press” can be configured by setting config.tap_hold.interrupt_response to "HoldOnKeyPress" in keymap.ncl.

Demonstrative Keymap

Let’s demonstrate tap-hold “hold on interrupting key press” behaviour using a keymap with a tap-hold key, and a keyboard key:

Given a keymap.ncl:

let K = import "keys.ncl" in
{
  config.tap_hold.interrupt_response = "HoldOnKeyPress",
  keys = [
    K.A & K.hold K.LeftCtrl,
    K.B
  ]
}

rolling key presses (press TH(A), press B, release TH(A), release B)

Rolling the tap-hold key with another key (i.e. interrupting the tap-hold key with another key press), for a tap-hold key configured with interrupt_response = "HoldOnKeyPress" resolves the tap-hold key as ‘hold’.

When the keymap registers the following input

[
  press (K.A & K.hold K.LeftCtrl),
  press (K.B),
  release (K.A & K.hold K.LeftCtrl),
  release (K.B),
]

Then the output should be equivalent to output from

[
  press (K.LeftCtrl),
  press (K.B),
  release (K.LeftCtrl),
  release (K.B),
]

interrupting tap (press TH(A), press B, release B, release TH(A))

Interrupting the tap-hold key with another key tap (press & release), for a tap-hold key configured with interrupt_response = "HoldOnKeyPress" resolves the key as “hold”.

When the keymap registers the following input

[
  press (K.A & K.hold K.LeftCtrl),
  press (K.B),
  release (K.B),
]

Then the output should be equivalent to output from

[
  press (K.LeftCtrl),
  press (K.B),
  release (K.B),
]

TapHold Key (configure interrupt response: hold on tap)

The tap hold key’s response to interruptions can be configured.

“Resolves as ‘Hold’ when interrupted by key tap” can be configured by setting config.tap_hold.interrupt_response to "HoldOnKeyTap" in keymap.ncl.

Demonstrative Keymap

Let’s demonstrate tap-hold “hold on interrupting key tap” behaviour using a keymap with a tap-hold key, and a keyboard key:

Given a keymap.ncl:

let K = import "keys.ncl" in
{
  config.tap_hold.interrupt_response = "HoldOnKeyTap",
  keys = [
    K.A & K.hold K.LeftCtrl,
    K.B
  ]
}

rolling key presses (press TH(A), press B, release TH(A), release B)

Rolling the tap-hold key with another key (i.e. interrupting the tap-hold key with another key press), for a tap-hold key configured with interrupt_response = "HoldOnKeyTap" resolves the tap-hold key as ‘tap’.

When the keymap registers the following input

[
  press (K.A & K.hold K.LeftCtrl),
  press (K.B),
  release (K.A & K.hold K.LeftCtrl),
  release (K.B),
]

Then the output should be equivalent to output from

[
  press (K.A),
  press (K.B),
  release (K.A),
  release (K.B),
]

interrupting tap (press TH(A), press B, release B, release TH(A))

Interrupting the tap-hold key with another key tap (press & release), for a tap-hold key configured with interrupt_response = "HoldOnKeyTap" resolves the key as “hold”.

When the keymap registers the following input

[
  press (K.A & K.hold K.LeftCtrl),
  press (K.B),
  release (K.B),
]

Then the output should be equivalent to output from

[
  press (K.LeftCtrl),
  press (K.B),
  release (K.B),
]

Layered Keys

“Layered Keys” are a lower-level key which implements layering functionality.

See Layers for a friendlier way to declare layers in a keymap.ncl file.

Demonstrative Keymap

Given a keymap.ncl:

let K = import "keys.ncl" in
{
  keys = [
    K.layer_mod.hold 0,
    K.A & { layered = [ K.B ] },
  ],
}

acts as the base key when no layer is active

If no layers are active, the key will be the key on the base layer.

When the keymap registers the following input

[
  press (K.A & { layered = [ K.B ] }),
]

Then the HID keyboard report should equal

{ key_codes = [K.A] }

acts as the key on that layer when its layer modifier held

When the keymap registers the following input

[
  press (K.layer_mod.hold 0),
  press (K.A & { layered = [ K.B ] }),
]

Then the HID keyboard report should equal

{ key_codes = [K.B] }

keymap.ncl

The Nickel code in ncl/ has some functions which help when writing keymap.ncl files.

Layers

Layers are a basic part of smart keyboard firmware.

Layers are like the Fn key on laptop keyboards, where holding the Fn key allows alternate functionality for other keys on the keyboard.

For examples of this key in other smart keyboard firmware, see e.g.:

Demonstrative Keymap

Layers can be used by setting using the layers field of a keymap.ncl.

Here, a keymap.ncl file with 2 keys, and 2 layers (base layer + 1 layer).

Given a keymap.ncl:

let K = import "keys.ncl" in
{
  layers = [
    [
      K.layer_mod.hold 0,
      K.A,
    ],
    [
      K.TTTT,
      K.B,
    ],
  ],
}

acts as the base key when no layer is active

If no layers are active, the key will be the key on the base layer.

When the keymap registers the following input

[
  press (K.A),
]

Then the HID keyboard report should equal

{ key_codes = [K.A] }

acts as the key on that layer when its layer modifier held

When the keymap registers the following input

[
  press (K.layer_mod.hold 0),
  press (K.B),
]

Then the HID keyboard report should equal

{ key_codes = [K.B] }

Layer String

Layers in keymap.ncl can be defined using strings.

keymap.ncl with layer string.

keymap.ncl supports defining each layer in the keymap with a string.

This is simpler than the equivalent nickel array using keys such as K.A, K.B, etc.

Each whitespace-delimited substring is then used as a field to lookup the key from keys.ncl.

For example:

{
  layers = [
    m%"
      Q W E R T Y
    "%,
  ],
}

keymap.ncl with layer string, with custom keys.

Often you’ll want to be able to use custom keys, even in layer strings.

This can be achieved by providing a custom_keys function, which is a keys extension. i.e. returns a record of keys, extending a given K keys record.

For example:

{
  custom_keys = fun K =>
    {
      MY_Q = K.Q & K.LeftShift
    },
  layers = [
    m%"
      MY_Q W E R T Y
    "%,
  ],
}

Chords

“Chords” (also sometimes called “combos”) allow pressing multiple keys together at the same time to behave as another key.

For examples of this feature in other smart keyboard firmware, see e.g.:

Demonstrative Keymap

Given a keymap.ncl:

let K = import "keys.ncl" in
{
  chords = [
      { indices = [0, 1], key = K.C, },
  ],
  keys = [
      K.A, K.B,
  ],
}

tapped key behaves as usual

When the keymap registers the following input

[
  press K.A,
]

Then the output should be equivalent to output from

[
  press (K.A),
]

tapped together behaves as chord

When the keymap registers the following input

[
  press K.A,
  press K.B,
]

Then the output should be equivalent to output from

[
  press (K.C),
]

pressed together with delay behaves as separate keys

When the keymap registers the following input

[
  press K.A,
]

And the keymap ticks 250 times

And the keymap registers the following input

[
  press K.B,
]

Then the output should be equivalent to output from

[
  press (K.A),
  press (K.B),
]