Introducing Tinker

From the first version of InKey in 2008 until recently, the keyboards that InKey supported were AutoHotKey (AHK) scripts. These could be extremely flexible and powerful, but the problem was that they could be too powerful: A malicious keyboard distributed by malware authors had the potential for causing significant damage. For this reason, InKey was distributed only through private distributors who provided support to their users and ensured that only legitimate keyboards were installed. InKey could not be publicly released without putting the public at risk of malicious keyboards.

Another shortcoming of AHK keyboards was that keyboard developers had to understand the peculiarities of the AHK syntax, so there was more of a learning curve than necessary.

InKey 2.0 no longer permits AHK keyboard scripts, so it can no longer open doors for malicious keyboards. Instead, we've been developing a keyboard description language called Tinker that makes it easy to describe keyboarding functionality without giving the keyboard any potentially-dangerous abilities. Also, it is freed from the peculiarities and limitations of AHK syntax.

The Tinker language is still evolving. As we translate existing InKey AHK keyboards into Tinker, we need to determine if there is any needed functionality that Tinker does not yet easily support, which we should implement. If you find areas where the needs of a keyboard that you are developing are not well-supported by Tinker, please let us know.

What You'll Need For Developing a Tinker Keyboard

An Overview of InKey's File Structure

Tutorial

To get started, do one of these two options:

Open your .tinker file in your text editor, such as Notepad++.

If you're using the Generic sample keyboard, you'll see that it contains the following:

// Generic sample keyboard
Settings:
  TinkerVersion: 2.000
...
q > 【☺】

Comments

The first line, beginning with //, is a comment. Wherever a double forward slash appears, everything else for the rest of the line is ignored.

Tinker also supports block comments, enabling a multi-line section of your file to be ignored as comments. A block comment begins with and ends with . These special characters can be obtained using the Tinker keyboard by double-tapping the right and left wedge keys: > and <

The Header Section

The next three lines are the header section of the file, beginning with Settings:and ending with a line containing three dots (...).

In this section we find an indented line specifying the TinkerVersion, the version of the Tinker format that this file uses. Set this to match the current version of InKey, or the lowest version of InKey that you've tested the keyboard to work with.

Keystroke Rules

After the header comes the keystroke rules. In the Generic sample keyboard, there is a sample rule, one that outputs a smiley face character when the q key is pressed. The character to be sent is enclosed in parameter brackets that you can type using the Tinker keyboard by double-tapping the square bracket keys: [ ]

Let's suppose you want to create a phonetic layout for Russian. You can get almost all the way there simply by mapping keys to characters, like this:

Settings:
  TinkerVersion: 2.000
...
B > 【Б】 b > 【б】 C > 【Ч】 c > 【ч】 D > 【Д】 d > 【д】

Notice that each key handler begins on a new line, without any indenting spaces.

Also notice how lowercase and uppercase  keys are identified on the left side, by using the lower/upper-case character. (It is also OK to specify an uppercase key using a plus sign (+) in front of the lowercase key. e.g. +b for the Shift + b key. In general, Tinker supports the AutoHotKey prefix symbols. e.g. >!b for RightAlt + b)

However, there are more letters in the Russian alphabet than we have keys for. There are several possible keyboarding methods that will enable us to make the other characters accessible. One of these is to use a multi-tap rule, where a quick double-tap on a key will produce a different character than pressing the same key two times but not as fast, analogous to the distinction between two single clicks and one double-click with a mouse, or spelling words with a phone's number pad. (InKey provides a setting for the user to customize the interval for counting multi-taps.) The way to specify a multi-tap rule is to list both characters inside the parameter brackets, separated with a space, like this:

A > 【А Я】
a > 【а я】

Go ahead and try this out:

Caps-Sensitivity

The Caps Lock key works with this Russian keyboard just as you'd expect: If Caps Lock is on, it makes things function as if the Shift key was pressed when it wasn't, and as if it wasn't pressed when it was. For most non-Roman keyboards, however, this functionality is not needed. Such keyboards should indicate this with a Settings key CapsSensitive: No, like this:

Settings:
   TinkerVersion: 2.000 
   CapsSensitive: No
...

Alternatively, if Caps sensitivity is desirable, but not for the 26 letter keys, a CapsKeys setting can specify exactly which keys are caps-sensitive. This is how to specify only the vowel keys as caps-sensitive:

Settings:
   TinkerVersion: 2.000 
   CapsKeys: aeiou
...

Hex Brackets

To Specify by Codepoint

It is possible to specify a character by its hexidecimal codepoint instead of literally, as above. This is done by enclosing the codepoint inhex bracketsthat you type with the Tinker keyboard by double-tapping the left and right curly brace keys: { } For example:

q > 【〔1230B〕】     // U+1230B is an SMP character: Akkadian cuneiform Winkelhaken 

It's a good idea to use hex brackets for any characters that may not be human-readable on their own. For example, the zero-width non-joiner (U+200C) character is not visible on its own, so can be specified like this:

x > 【〔200C〕】
To Specify by Defined Name

Alternatively, you can define names to be used. This can help to make complex rules easier to read. First use the define keyword followed by the name to define, followed by the value, which may be a combination of codepoints in hex brackets and literal characters. Then, elsewhere in the file, use the defined name inside hex brackets. For example:

define ZWNJ 〔200C〕

x > after 【〔ZWNJ〕】 send 【】 // To prevent repeated typing of ZWNJs, send nothing if there's already ZWNJ. 
| 【〔ZWNJ〕】 // Otherwise, send ZWNJ

Alternate Rules

The above example contains two rules. The first rule begins after the > operator; each additional rule begins indented on a successive line and is preceeded by the | operator.

Each rule is evaluated in order, stopping when a rule has applied. A rule without keywords always applies, so should only be used as the final rule in the set.

In this example, we used the after 【 】 send 【 】 rule to specify what text to send (in this case, nothing) after what context (in this case, after an existing ZWNJ). We'll cover this kind of rule later. 

Maps

One of the most common functions a key must perform is to replace an already-typed character with some other character. For example, the International Phonetic Alphabet (IPA) contains several letters with rightward hooks, such as ɓ, ƈ, ɗ, ɠ, ɱ, and ŋ. The IPA keyboard allows these to be typed by pressing the right wedge key > after the base letter. The association between the base character and the modified character is laid out in a map rule like this:

> > map 【b ɓ】【c ƈ】【d ɗ】【m ɱ】【n ŋ】   // make character rightward-hooked
  | 【>】                                 // otherwise send right-wedge itself

Map rules sometimes apply to more than a single character. The string of text for the match or replacement may be comprised of more than one character. For example, in the IPA keyboard, the ampersand key & is used to join the two prior characters into a single combined character. Here's a simple rule to do this:

& > map 【dz ʣ】【dʒ ʤ】【dʑ ʥ】【ts ʦ】【tʃ ʧ】【tɕ ʨ】【fŋ ʩ】【ls ʪ】【lz ʫ】【lʒ ɮ】
  | 【&】

If a second press of the modifier key should change the character yet again, simply add as many additional elements as are needed, separated by spaces. For example, while the IPA keyboard provides a few different modifier keys, it can be difficult to remember which one to use. As a backup alternative, the equals key = can be pressed repeatedly to cycle through all forms of a given base letter. A trimmed-down version of that rule looks like this:

= > loopMap 【a ɑ ɐ α ᶏ】【ae æ ᴂ aə】【oe œ oə】【e ə ɜ ɛ ɘ】
            【h ɥ ħ ɦ ђ ɧ】【y ʏ ɥ ʯ】
  | 【=】 // otherwise send equals sign itself

A few other points to note in this example:

Rules with Regular Expressions

The Tinker format allows for rules with great power and flexibility be means of regular expressions, also called regex. (For a more thorough introduction, see this regex tutorial. Note that Tinker uses the PCRE flavor of regex.) One of the most common uses of regex is the "set expression" (a.k.a. "character class"), a set of characters, any one of which can match the pattern.  A set expression is defined by enclosing the set of characters in square brackets.  For example, suppose that when the tilde key ~ is pressed, if the previous letter was a vowel or the letter n, you want to send a combining tilde character (U+0303), otherwise, you want to send a regular tilde character (~).  The regex for that set of characters of which any can match is [aeiounAEIOUN], so the whole thing looks like this:

~ > after 【[aeiounAEIOUN]】 send 【〔303〕】 //  Send combining tilde after vowel or n.
| 【~】 //  Otherwise, just send ordinary tilde

If the set of characters you need to define includes all characters in a range of Unicode codepoints, you can specify that range using a hyphen in the set.  For example, the Devanagari consonants comprise the contiguous range from  to , so the set of consonants can be defined as  [क-ह].  This is important because Devanagari has two forms of each vowel:  The dependent form (such as ि) that must follow a consonant, and the independent form (such as ) that does not require a preceding consonant.  So simple rules that output the dependent vowel after a consonant and the independent vowel otherwise look like this:

i > after 【[-]】 sendि//  Send dependent vowel after any consonant
  | 【//  Otherwise, send the independent form of the vowel

Actually, this rule is not perfect yet, because the consonant may optionally be followed by a "nukta" low-dot character (). We can specify this in the regex by putting a nukta character after the character set, followed by a question mark to indicate that the nukta is optional. If this regex is something we might use in multiple rules, or just for better legibility, we can pull it out into a define statement:

define Cons [-]़?              //  Any consonant character, followed by an optional nukta
 
i > after 【〔Cons〕】 sendि//  Send dependent vowel after any consonant
  | 【//  Otherwise, send the independent form of the vowel

Regular expressions provide an extremely powerful syntax for specifying patterns of text.  You can match text patterns with reference to the characters' Unicode categories, such as letters, marks, symbols, numbers, etc. using the \p{} syntax.  For example, \p{M} matches any character in the Marks category (such as diacritics), while \p{L} matches any letter.  To match a letter followed by any number of optional diacritics, use the * quantifier: \p{L}\p{M}*

For example, in the IPA keyboard, we use the underscore key _ to produce the "combining minus sign below" character (U+0320).  However, we still want the user to be able to type the underscore character itself, such as in phonetic rules. (e.g.  n → m / _ b) In such contexts, it would be annoying to the user if the underscore key always produced a combining minus sign below character, as  it makes no sense to produce a combining character when there is no prior letter for it to combine with.  It should only produce U+0320 when it comes after a letter (which may be optionally followed by other diacritics).  Otherwise, it should produce the underscore character itself.  The rules, then, would look like this:

_ > after 【\p{L}\p{M}*】 send〔320〕//  combining minus sign below
  | 【_//  Otherwise, just send ordinary underscore

Sometimes, in response to a keystroke, we want existing text to be replaced and/or rearranged.  For example, suppose that we want the backtick key to convert the Devanagari  character into the  character, even if there are intervening mark characters.  For this, the Replace with rule does what we need:

_ > replace(\p{M}*)】 with$1】 

In this regex pattern of the text to be replaced, (\p{M}*) means "a group of zero or more characters with a Unicode category of Mark".  In the replacement text, $1 is a "back-reference" to that group, meaning that whatever mark characters may have been found, put them back at this point.  

Note that regular expressions are implicitly anchored to the end of the context.  You should not add anchors (such as $) to the regex string.

If you know how to employ regular expressions, your keyboard can define extremely powerful functionality in a single rule.  For example, suppose that you want the Alt+Minus key to convert the preceding word to its Pig Latin form.  For the sake of simplifying the example, we'll assume that the words are all lowercase.  Here are the rules for how the key should behave:

For the first rule, one way to say "any word-initial consonant cluster followed by any sequence of letters" is to say \b([bcdfghjklmnpqrstvwxyz]+)(\p{L}+). We can then refer back to the two parts grouped by parentheses as $1 and $2 respectively.  Since we want to swap their order and add "ay" to the end, the replacement pattern is $2$1ay.

For the second rule, one way to say "any word that begins with a vowel followed by any optional letters" is to say \b[aeiou]\p{L}*

Our Tinker rules, then are like this:

// Alt-minus key as a pig-latinizer. 
!- > replace 【\b([bcdfghjklmnpqrstvwxyz]+)(\p{L}+)】 with 【$2$1ay】  //  Reorder initial cons cluster.
   | after 【\b[aeiou]\p{L}*】 sendway// For vowel-initial word, just append "way" 
   | beep // Nothing to pig-latinize 

Obtaining this functionality in a keyboarding system (such as KMN format) that does not support quantifiers (such as * and +) requires a separate rule for each possible combination.  e.g. one rule for specifically for15-letter words with 3 initial consonants, etc.  While our pig-latinizer may seem like a somewhat artificial example, in fact, the same kind of behavior occurs in complex writing systems.  Without quantifiers, either the user is forced into less-intuitive keying sequences, or the rules are hairy to write and maintain.  For example,  in Devanagari hand-writing, the "flying reph" character is typically written on top of a syllable after the syllable has been composed, and so it is good keyboard design to provide a means to key the flying reph after (or during) the composition of the syllable.  The phonological meaning of the flying reph, however, is that there is an "r" sound at the onset of the syllable.  Accordingly, the underlying Unicode sequence for the flying reph is the "ra" and "virama" characters inserted at the start of the orthographic syllable.  If a keyboard is to assign a key to insert these characters at the appropriate point, the difficulty lies is recognizing the extent of the orthographic syllable.  In the traditional approach, it would be rather difficult to account for all the possibilities of what might constitute a "syllable": a consonant; optionally followed by a nukta; optionally followed by one or more joined consonants (virama optionally followed by ZWJ or ZWNJ, optionally followed by another consonant and optional nukta); optionally followed by one or Mark characters (which include dependent vowels and diacritics). As complicated as that sounds, a regular expression can describe it easily enough:

define Cons [-]़?
define ZWChar [〔200C〕〔200D〕]

R > replace 【〔Cons〕(〔094D〕〔ZWChar〕*〔Cons〕)*\p{M}*】 with 【र्$0】 // Reph is being typed during/after the syllable
  | 【र्// Otherwise reph is being typed before the syllable

The replacement expression uses $0 to refer back to the entire matched text, effectively inserting र् in front of it all.  If there is not a syllable in the context, e.g. after space or punctuation, the second rule outputs र् to precede the syllable about to be typed.  Attempting this with KMN rules would require 160 rules to account for each pattern combination.  Regex is a very good friend to have.

Custom Normalization of Text

Regular expressions can also be useful for reordering characters.  Suppose that we want to reorder the characters into a certain order, no matter in which sequence they were typed.  For this example, let's begin with a simple keyboard that outputs circled digits when you press the keys 1 through 5:

1 > 【①】
2 > 【②】
3 > 【③】
4 > 【④】
5 > 【⑤】

Suppose now that you want to reorder these characters, such that sequential digits are always resorted into the order: ①②③④⑤  With this, when you press 5, the ⑤ can always go on the end without reordering.  When you press 4, the ④ needs to get moved in front of any ⑤'s that immediately precede it.  Likewise down to when you press 1, it needs to get moved in front of any ②,③,④ or ⑤'s in a sequence in front of it.  We describe this behavior with a regex replacement, specifying both the pattern to replace, and what to replace it with.  Since our replacement needs to refer back to the entire matched pattern, it can do so with a back-reference of $0.  So we simply insert a  replace with rule before our original simple rule:

1 > replace 【[②③④⑤]+】 with 【①$0】  // Insert 1 before any 2-5's
  | 【①】

2 > replace 【[③④⑤]+】 with 【②$0】  // Insert 2 before any 3-5's
  | 【②】

3 > replace 【[④⑤]+】 with 【③$0】  // Insert 3 before any 4-5's
  | 【③】

4 > replace 【[⑤]+】 with 【④$0】  // Insert 1 before any 4's
  | 【④】

5 > 【⑤】

Now, to take this example to really dizzying lengths, there are some cases in which the way you reorder depends on the context.  Context-dependent reordering is a rare need, but it does occur in some writing systems. For this example, suppose that if the sequence of digits follows the letter b, the order will be entirely backwards, that is, ⑤④③②①.  For this condition of the sequence following b, we need to an extra rule to each digit handler:

1 > after 【b[⑤④③②①]*】 send 【①】   // Just append 1 if b preceeds digits.
  | replace 【[②③④⑤]+】 with 【①$0】  // Insert 1 before any 2-5's
  | 【①】

2 > after 【b[⑤④③②]*】 replace 【①*】 with 【②$0】   // Insert 2 before 1's if b preceeds digits.
  | replace 【[③④⑤]+】 with 【②$0】  // Insert 2 before any 3-5's
  | 【②】

3 > after 【b[⑤④③]*】 replace 【[②①]*】 with 【③$0】   // Insert 3 before 1-2's if b preceeds digits.
  | replace 【[④⑤]+】 with 【③$0】  // Insert 3 before any 4-5's
  | 【③】

4 > after 【b[⑤④]*】 replace 【[③②①]*】 with 【④$0】   // Insert 4 before 1-3's if b preceeds digits.
  | replace 【[⑤]+】 with 【④$0】  // Insert 4 before any 5's
  | 【④】

5 > after 【b⑤*】 replace 【[④③②①]*】 with 【⑤$0】   // Insert 5 before 1-4's if b preceeds digits.
  | 【⑤】

Note that the syntax after 【】 replace 【】 with 【】 allows you to specify a matching pattern that precedes the part that gets replaced.  In regex terminology is this called a "look-behind".  While many flavors of regex require look-behind expressions to be fixed-width, Tinker's  after clause permits you to use quantifiers like * and +, as we did here.

Combining Regex and Maps

Sometimes, neither regex nor a map is sufficient on its own to compactly describe the functionality you need.  What actually we need is both regex and a map.  Consider a keyboard with a backtick key ` that turns one shape of a vowel into an alternate, cycling around:

` > loopMap 【a ɑ ə】【e ɛ】【i ɩ ɪ】【o ɔ】【u ʊ】【y ɥ】

Now suppose that the vowel might already have nasalization and/or various tone modifiers on it. If so, this map will not match the existing context.  However, by using regular expressions, we can describe this behavior easily. For example, regex can capture a group of zero or more Mark characters (Unicode category "M”) as \p{M}* and then refer back to this group as $1. Tinker's syntax for this replacement is:

` > replace 【$F\p{M}*】 with 【$R$1】 usingLoopMap 【a ɑ ə】【e ɛ】【i ɩ ɪ】【o ɔ】【u ʊ】【y ɥ】

We use $F to refer to each "Find” element from the map, and $R to refer to the corresponding "Replace” element. This syntax allows us to express rather complex behavior in a simple easy-to-read format.

For maps that act only in one direction, the clause usingMap should be used in place of usingLoopMap. As with the other replace rules, these can also begin with an an after clause to specify a regex look-behind context.

Thus, the rules supporting regex are:

after 【regex】 send 【text】
replace 【regex】 with 【replacement-text】
replace 【F-regex】 with 【R-replacement-text】 usingMap 【map】
replace 【F-regex】 with 【R-replacement-text】 usingLoopMap 【map】
after 【look-behind-regex】 replace 【regex】 with 【replacement-text】
after
【look-behind-regex】 replace 【F-regex】 with 【R-replacement-text】 usingMap 【map】
after
【look-behind-regex】 replace 【F-regex】 with 【R-replacement-text】 usingLoopMap 【map】

where the parameters are of the following types:

Deadkeys

A deadkey is a key that makes no visible difference when pressed, but when you press the next key, the character produced will be different because of the deadkey, such as è or ë instead of e. 

Note that generally, it's poor keyboard design to use deadkeys.  They were necessary back when the technology did not support replacing already-typed characters with other characters.  MSKLC keyboards cannot do this, so must use deadkeys.  However, for the sake of a habituated user base, you may occasionally be asked implement deadkey functionality. We do this by entering a number from 1 to 8 in hex brackets, like this: 

` >〔1〕】  // backtick key produces deadkey #1
~ > 〔2〕】  // tilde key produces deadkey #2
  

The handler for the e key, then, must produce one form after each deadkey, and a normal e otherwise. Here's one way to specify such rules:

e > after 【〔1〕】 send 【è】   //  1st rule: Backtick deadkey produces e with grave accent
  | after 【〔2〕】 send 【ë】   //  2nd rule: Tilde deadkey produces e with diaeresis 
  | 【e】                   //  Otherwise, just send ordinary e

Another method employs a map:

e > map 【〔1〕 è】 【〔2〕 ë】   //  2nd rule: Tilde deadkey produces e with diaeresis 
  | 【e】                   //  Otherwise, just send ordinary e

Functions

Consider these similar key-handlers for a Devanagari keyboard. They send a dependent vowel after a consonant, or an independent vowel otherwise:

e > after 【[क-ह]़?】 send 【े】
  | 【ए】

E > after 【[क-ह]़?】 send 【ै】
  | 【ऐ】

i > after 【[क-ह]़?】 send 【ि】
  | 【इ】

I > after 【[क-ह]़?】 send 【ी】
  | 【ई】

o > after 【[क-ह]़?】 send 【ो】
  | 【ओ】 

O > after 【[क-ह]़?】 send 【ौ】
  | 【औ】 

u > after 【[क-ह]़?】 send 【ु】
  | 【उ】

U > after 【[क-ह]़?】 send 【ू】
  | 【ऊ】

The duplication between them can be eliminated by defining a function containing the common functionality, like this:

function SendVowel 【dep】【syl】
   after 【[क-ह]़?】 send 【〔dep〕】
   | 【〔syl〕】

e > SendVowel 【े】【ए】
E > SendVowel 【ै】【ऐ】
i > SendVowel 【ि】【इ】
I > SendVowel 【ी】【ई】
o > SendVowel 【ो】【ओ】 
O > SendVowel 【ौ】【औ】 
u > SendVowel 【ु】【उ】
U > SendVowel 【ू】【ऊ】

Note the following:

User-Configured Options and Pre-processor Conditionals

Keyboard developers often find that different users have different preferences over minor differences. For example, whether to use smart quotes by default, or whether to use Western or script digits by default, or which keystroke, if any, should be assigned to a particular functionality.

The last thing the developer wants to do is have a multitude of keyboards to maintain, differing only by minor functionalities.

User-configured options are specified in the header of a Tinker file, like this:

Settings:
 TinkerVersion: 2.000
 CapsSensitive: No

Options:
  - checkbox: 
    name: ScriptDigit
    label: Use Devanagari digits rather than Western digits by default 
    default: 0
  - checkbox: 
    name: SmartQuotes
    label: Enable smart quotes
    default: 0
  - keystroke: 
    name: AltShapeKey
    label: Alternate shape
... 

The user will be able to make a selection between these options by right-clicking the InKey icon, selecting the menu item for Configure keyboard options, and then selecting the keyboard name, and then clicking on Configure keyboard_name-specific options.

Currently, InKey's keyboard-configuration window only supports the checkbox items. The handling of keystroke items will be added later, but for now can be manually configured in the [Options] section of the user configuration (.kbd.ini) file, which is typically located under the %appdata%\InkeySoftware folder. If the file does not yet exist, it is initialized by copying the distribution version from the keyboard folder. (Note: When developing a keyboard, be sure to copy the finalized configuration file back to the keyboard folder before packaging it for distribution.)

The section of the user configuration file that corresponds to the above Tinker header might look like this:

[Options]
ScriptDigit=0
SmartQuotes=1
AltShapeKey=\

Points to note about the Options section in the Tinker header:

The keystroke rules in the Tinker file may reference these settings in ways like these:

// Do digit keys, whether Devanagari or Western
function DoDigit 【western】【dev】【reverse】
     after 【[0-9]】 send 【〔western〕】 // Make this digit Western if following a Western digit.
     | after 【[०-९]】 send 【〔dev〕】    // Make this digit Dev if following a Dev digit.
     | ⌘if 〔ScriptDigit〕 ^ 〔reverse〕 ⌘then 【〔dev〕】 ⌘else 【〔western〕】 ⌘endif 
           // Otherwise, decide script based on default setting XOR'ed with reverse parameter 

  // Normal digit keystrokes
0  > DoDigit 【0】【०】【0】
1  > DoDigit 【1】【१】【0】
2  > DoDigit 【2】【२】【0】
3  > DoDigit 【3】【३】【0】
4  > DoDigit 【4】【४】【0】
5  > DoDigit 【5】【५】【0】
6  > DoDigit 【6】【६】【0】
7  > DoDigit 【7】【७】【0】
8  > DoDigit 【8】【८】【0】
9  > DoDigit 【9】【९】【0】

  // RightAlt digit keystrokes
>!0 > DoDigit 【0】【०】【1】
>!1 > DoDigit 【1】【१】【1】
>!2 > DoDigit 【2】【२】【1】
>!3 > DoDigit 【3】【३】【1】
>!4 > DoDigit 【4】【४】【1】
>!5 > DoDigit 【5】【५】【1】
>!6 > DoDigit 【6】【६】【1】
>!7 > DoDigit 【7】【७】【1】
>!8 > DoDigit 【8】【८】【1】
>!9 > DoDigit 【9】【९】【1】

// Smart Quote functionality
⌘if 〔SmartQuotes〕 ⌘then
" > loopMap 【" “ ”】
  | after 【[^\[ ‘\t\n\(⌊\[\{]】 send 【”】 
  | 【“】
   
' > loopMap 【' ‘ ’】
  | after 【[^\[ “\t\n\(⌊\[\{]】 send 【’】 
  | 【‘】
⌘endif

// Key for making an alternate shape
〔AltShapeKey〕 > loopMap 【ए े】【ऐ ै】【इ ि】【ई ी】【ओ ो】【औ ौ】【उ ु】【ऊ ू】

The ⌘if ⌘then ⌘else ⌘endif pre-processor conditional structure provides compile-time (as opposed to run-time) selection of functionality.

The above user configuration file settings associated the backspace key \ with the 〔AltShapeKey〕 handler. If the setting had been blank, the entire handler would have been ignored.

If a keyboard makes extensive use of user-configurable keystroke options, a layout chart provided as a PDF will not reflect this configuration. Fortunately, there is a mechanism to automatically generate a layout chart that includes the keystrokes specified in the user settings file. See Dynamic Layout Charts for instructions.

Current Known Limitations of Tinker

So far, Tinker only supports one ruleset (set of alternate rules, separated by the | operator) per key being handled, and the rules in these rulesets cannot be combined with other actions or condition tests.

InKey's on-screen keyboard functionality has not yet been reimplemented for Tinker.

We have not yet implemented a syntax to permit the setting/checking of a phase (a temporary state that, unlike deadkeys, exists independently of the context until the next keystroke) or a mode (like a phase, but the state endures for the entire session. e.g. toggling whether smart quotes or plain quotes are active).

We have not yet implemented a mechanism to force a keyboard to normalize according to NFC or NFD. Currently this is determined purely by which characters the keyboard rules produce.

Functions currently operate as a preprocessor replacement, not as a run-time function call that returns a boolean value, but that's the direction we'd like to go.