Below are the smart key features implemented for smart keymap:
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] }
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] }
The TapHold key can behave differently depending on whether the key has been tapped or held.
e.g.
Quickly pressing and releasing a TapHold key results in a the ‘tap’ behaviour.
Pressing the TapHold key for a long enough period results in the ‘hold’ behaviour.
Pressing another key while the TapHold key is pressed “interrupts” the TapHold key, resulting in the ‘hold’ behaviour.
For examples of this key in other smart keyboard firmware, see e.g.:
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
]
}
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] }
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 } }
The “timeout” before a tap-hold is considered as held can be
configured by the field config.tap_hold.timeout
in
keymap.ncl
.
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 } }
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 = [] }
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.
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 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),
]
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),
]
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
.
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 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 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),
]
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
.
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 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 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” are a lower-level key which implements layering functionality.
See Layers for a friendlier way to declare layers in a keymap.ncl file.
Given a keymap.ncl:
let K = import "keys.ncl" in
{
keys = [
K.layer_mod.hold 0,
K.A & { layered = [ K.B ] },
],
}
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] }
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] }
The Nickel code in ncl/
has some functions which help
when writing keymap.ncl
files.
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.:
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,
],
],
}
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] }
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] }
Layers in keymap.ncl
can be defined using strings.
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
"%,
],
}
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” (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.:
Given a keymap.ncl:
let K = import "keys.ncl" in
{
chords = [
{ indices = [0, 1], key = K.C, },
],
keys = [
K.A, K.B,
],
}
When the keymap registers the following input
[
press K.A,
]
Then the output should be equivalent to output from
[
press (K.A),
]
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),
]
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),
]