Casing support
Support for case-sensitivity in a touch keyboard is surprisingly complex. In this guide, we will look the various features that Keyman includes to implement comprehensive casing support, in particular:
- Determination of casing keys (Keyman 14+)
- Caps Lock layer vs Shift layer (Keyman 15+)
- Start of sentence detection (Keyman 15+)
- Automatic return to default layer (Keyman 15+)
Alongside keyboard support for casing, a lexical model may also contain casing rules. See the Lexical Model search term to key topic for more details.
Determination of casing keys
The &casedkeys
system store determines which keys
have automatic Caps Lock casing behaviour. This impacts desktop and touch
keyboards.
Caps Lock layer
To add support for double-tapping Shift to reach a 'sticky' Caps Lock layer, introduce a separate 'caps' layer into the layout file. The presence of this layer will enable the double-tap gesture in Keyman on the Shift key.
Automatic return to default layer
In earlier versions of Keyman, it was common to include nextlayer
controls on
individual output keys (particularly in shift or symbol layers) to return to the
previous layer after touching them. In Keyman 15 and later versions, this can be
implemented with more control using the begin PostKeystroke
rule.
begin PostKeystroke
is also used for start-of-sentence detection.
Start of Sentence detection
Start of sentence detection comes into play both when a user enters a text field, or moves the cursor within a text field, and after the user types a keystroke.
When entering a text field, or moving the cursor, the begin NewContext
rule will be called. The keyboard may not emit any
characters when this rule is called, but may change the current layer or set
other variable store values.
After each keystroke, the begin PostKeystroke
rule is called. While some of
the same functionality can be replicated by including it in the begin Unicode
processing, this entry point is called after both model input (e.g. selecting a
word from the banner), and after nextlayer
processing from the touch layout,
giving the keyboard a chance to adjust the layer and other variable stores.
We do need to be careful, however, not to override the user's recent touch of a
layer switch key. Keyman provides the &newLayer
system store
and the &oldLayer
system store to allow the
keyboard to detect this and avoid overriding it.
Start of sentence detection can also be used for other contextual layer controls, for example, moving to the numeric layer when digits are found.
Worked example
This example keyboard demonstrates how to integrate all the above features for an English keyboard. Adding more nuance is left as an exercise for the reader!
c Sample English keyboard with start-of-sentence detection
store(&VERSION) '10.0'
store(&NAME) 'Casing Support'
store(&TARGETS) 'any'
store(&VISUALKEYBOARD) 'casing.kvks'
store(&LAYOUTFILE) 'casing.keyman-touch-layout'
c Note three entry points, rather than the traditional single entry point
begin Unicode > use(main)
begin NewContext > use(NewContext)
begin PostKeystroke > use(PostKeystroke)
c This tells Keyman which keys should have casing behavior applied
store(&CasedKeys) [K_A] .. [K_Z]
c We'll define some useful stores here
store(key) [K_A] .. [K_Z] [SHIFT K_A] .. [SHIFT K_Z]
store(out) 'a' .. 'z' 'A' .. 'Z'
store(caps) 'A'..'Z'
store(digit) '0'..'9'
group(NewContext) readonly
c Any time we get a new context, by mouse click, tap,
c cursor movement, new document, etc, we'll try and
c determine the best layer to use
nomatch > use(detectStartOfSentence)
group(PostKeystroke) readonly
c We get here after every keystroke and model action is processed
c Okay, let's stay on the numeric layer if we are there already
if(&newLayer = "") if(&layer = 'numeric') any(digit) > context
c Don't swap off the caps lock layer automatically
if(&layer = 'caps') > context
c no other changes, so detect sentence or layer change, as long
c as the user hasn't attempted to change layer themselves.
if(&newLayer = "") > use(detectStartOfSentence)
group(detectStartOfSentence) readonly
c We have a shared group for the start of sentence detection now
c which will set the current layer according to the current context.
c Some common end-of-sentence punctuation
store(sentencePunctuation) '.?!'
c If we are at the start of a text field, we're at the 'start of a
c sentence', so we'll move to the Shift layer
nul > layer('shift')
c Otherwise, we'll move to the Shift layer only if we see end of a
c sentence followed by one or two spaces.
any(sentencePunctuation) ' ' > layer('shift')
any(sentencePunctuation) ' ' > layer('shift')
c And because we haven't see any of these situations above, we'll
c move to the default layer. Note that this effectively drops us
c back to the default layer after every keystroke that isn't blocked
c by the `PostKeystroke` or early `detectStartOfSentence` rules.
nomatch > layer('default')
group(main) using keys
+ any(key) > index(out, 1)