Smart Keymap Features

Smart Keys

Below are the smart key features implemented for smart keymap:

HID Keyboard Key

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] }

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
  ]
}

tap hold key 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] }

tap hold key 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),
]

TapHold Key (configure required_idle_time)

The required_idle_time config for tap hold keys means that tap hold keys act as ‘tap’ if they are pressed before the required idle time has passed since the previous keymap input event (press/release).

This helps prevent accidental ‘resolved-as-hold’ tap-hold key presses when typing quickly.

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

Demonstrative Keymap

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

Given a keymap.ncl:

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

tap hold key resolves as tap when pressed before required idle time

Pressing a tap-hold key immediately resolves it as ‘tap’ when it’s pressed after another key, within the required idle time.

When the keymap registers the following input

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

And the keymap ticks 50 times

And the keymap registers the following input

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

Then the output should be equivalent to output from

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

tap hold key resolves as tap when tapped after required idle time

Whereas, the tap-hold key behaves as usual if the keymap is idle for the required time.

When the keymap registers the following input

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

And the keymap ticks 110 times

And the keymap registers the following input

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

Then the output should be equivalent to output from

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

tap hold key resolves as tap when tapped after required idle time

When the keymap registers the following input

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

And the keymap ticks 110 times

And the keymap registers the following input

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

And the keymap ticks 250 times

Then the output should be equivalent to output from

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

Layered Key

“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 1,
    K.A & { layered = [ K.B ] },
  ],
}

layered key 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] }

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

When the keymap registers the following input

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

Then the HID keyboard report should equal

{ key_codes = [K.B] }

Caps Word Key

The “Caps Word” key can be thought of as “Caps Lock, for a single word”.

Where Caps Lock shifts all keys until it is disabled, Caps Word shifts while alphabetical keys (and underscore) are typed.

A motivating use case is typing out CONSTANTS_LIKE_THIS, automatically leaving the caps word mode when space is hit.

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

Demonstrative Keymap

Given a keymap.ncl:

let K = import "keys.ncl" in
{
  keys = [
    K.caps_word.toggle,
    K.A,
    K.B,
    K.Space,
  ]
}

caps word key activates when tapped and deactivates after space key pressed

When the keymap registers the following input

[
  press K.caps_word.toggle,
  release K.caps_word.toggle,
  press K.A,
  release K.A,
  press K.Space,
  release K.Space,
  press K.A,
  release K.A,
]

Then the output should be equivalent to output from

[
  press (K.LeftShift),
  press K.A,
  release K.A,
  release (K.LeftShift),
  press K.Space,
  release K.Space,
  press K.A,
  release K.A,
]

TapDance Key

The TapDance key can behave differently depending on how many times it is tapped.

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 & { tap_dances = [K.B, K.C] },
  ]
}

tap dance key acts as its first definition when tapped once

When the keymap registers the following input

[
  press (K.A & { tap_dances = [K.B, K.C] }),
  release (K.A & { tap_dances = [K.B, K.C] }),
]

And the keymap ticks 250 times

Then the output should be equivalent to output from

[
  press K.A,
  release K.A,
]

tap dance key acts as its first definition when pressed once and held

When the keymap registers the following input

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

And the keymap ticks 250 times

Then the output should be equivalent to output from

[
  press K.A,
]

tap dance key acts as its third definition when tapped three times

When the keymap registers the following input

[
  press (K.A & { tap_dances = [K.B, K.C] }),
  release (K.A & { tap_dances = [K.B, K.C] }),
  press (K.A & { tap_dances = [K.B, K.C] }),
  release (K.A & { tap_dances = [K.B, K.C] }),
  press (K.A & { tap_dances = [K.B, K.C] }),
  release (K.A & { tap_dances = [K.B, K.C] }),
]

Then the output should be equivalent to output from

[
  press K.C,
  release K.C,
]

Sticky Modifiers Key

The “Sticky Modifiers” key is keymap implementation of the “sticky key” accessibility feature that many desktop environments have.

If the sticky modifier key is tapped (without interruption), it modifies the next key press.

If the sticky modifier key is interrupted by another key press, then it behaves as a regular modifier key.

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

Demonstrative Keymap

Given a keymap.ncl:

let K = import "keys.ncl" in
{
  keys = [
    (K.sticky K.LeftShift),
    (K.sticky K.LeftCtrl),
    K.A,
    K.B,
  ]
}

tapping sticky modifier key modifies next key press

When the keymap registers the following input

[
  press (K.sticky K.LeftShift),
  release (K.sticky K.LeftShift),
  press K.A,
]

Then the output should be equivalent to output from

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

tapped sticky modifier keys stack

When the keymap registers the following input

[
  press (K.sticky K.LeftShift),
  release (K.sticky K.LeftShift),
  press (K.sticky K.LeftCtrl),
  release (K.sticky K.LeftCtrl),
  press K.A,
]

Then the output should be equivalent to output from

[
  press K.LeftShift,
  press K.LeftCtrl,
  press K.A,
]

sticky modifier releases when modified key releases

When the keymap registers the following input

[
  press (K.sticky K.LeftShift),
  release (K.sticky K.LeftShift),
  press K.A,
  release K.A,
]

Then the output should be equivalent to output from

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

sticky modifier key acts as regular key when interrupted by tap

When the keymap registers the following input

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

Then the output should be equivalent to output from

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

Sticky Modifiers Key (configure release: OnNextKeyPress)

The config.sticky.release can be set to "OnNextKeyPress" so that the sticky key releases when the next key is pressed after the modified key.

This helps with rolling key presses.

Demonstrative Keymap

Given a keymap.ncl:

let K = import "keys.ncl" in
{
  config.sticky.release = "OnNextKeyPress",
  keys = [
    (K.sticky K.LeftShift),
    (K.sticky K.LeftCtrl),
    K.A,
    K.B,
  ]
}

the sticky modifier releases when the next key is pressed

When the keymap registers the following input

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

Then the output should be equivalent to output from

[
  press (K.LeftShift),
  press K.A,
  release (K.LeftShift),
  press 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 1,
      K.A,
    ],
    [
      K.TTTT,
      K.B,
    ],
  ],
}

layers acts as the base 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] }

layers acts as active layer when its layer modifier is held

When the keymap registers the following input

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

Then the HID keyboard report should equal

{ key_codes = [K.B] }

Layers (Default Layer)

The K.layer_mod.set_default key allows setting the Default layer.

Demonstrative Keymap

Given a keymap.ncl:

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

tapping the set default layer modifier key changes the default layer

When the keymap registers the following input

[
  press (K.layer_mod.set_default 1),
  release (K.layer_mod.set_default 1),
  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 key definitions

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,
  ],
}

chorded key behaves as usual when tapped individually

When the keymap registers the following input

[
  press K.A,
]

Then the output should be equivalent to output from

[
  press (K.A),
]

chorded keys 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),
]

chorded keys 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),
]