-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathInKeyLib.ahki
More file actions
1421 lines (1257 loc) · 51.4 KB
/
InKeyLib.ahki
File metadata and controls
1421 lines (1257 loc) · 51.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
; ***** InKeyLib.ahki *****
; Provides the programming interface to the InKey System
#NoEnv
#Warn All, OutputDebug
#NoTrayIcon ; Comment this line out during keyboard development to see when keyboards are loaded.
#SingleInstance force ; InKey keyboard scripts must be single-instance.
#LTrim ; This command causes leading whitespace in continuation sections (e.g. multi-line rota strings) to be trimmed.
; If you prefer otherwise, use #LTrim Off
K_InKeyLibVersion = 1.913 ; At the top of your script, the very first line of code
; should set K_MinimumInKeyLibVersion to this number.
; This will ensure that if a user has an older version of InKeyLib.ahki that
; does not support the functionality that your script requires, he will be
; required to update his InKeyLib.ahki file to at least this version.
; If your script does not need the functionality that newer versions of
; InKeyLib.ahki provide, you can use an older version of InKeyLib.ahki as you
; develop your script, so that users who do not have the latest version can still
; use your script. Older versions (going back to version 0.089) can be found
; at www.inkeysoftware.com
; 0.100 Adds support for AutoHotKey_L, which includes better Unicode support.
; 0.101 Adds support for compiling InKey with AutoHotKey_L, which includes better Unicode support.
; 0.102 Improves support for CAPSLOCK functionality in default handlers. Adds UseUpperCase() function.
; 0.300 SendText() and InsertText() functions
; 0.900 MAJOR: to be documented
; 1.913 InRota() is now case-sensitive
K_ProtocolNum = 5 ; When changes are made to the communication protocol between InKey and InKeyLib.ahki, this number is incremented in both.
; This ensures that communication conventions between InKey and a keyboard will match.
to := " "
goto K_DO_INITIALIZE
;=========================================
; These are the functions that may be called by a keyboard script:
; Note: Functions that currently utilize a uFlags parameter will probably lose that . Don't depend on it. Better to use SetPhase() or SetDeadkey() as appropriate.
Send(ByRef CharString) {
; Send a text string
; e.g. Send("द्व")
SendData(0x901A, CharString)
return 1
}
; ________InCase()____________________________________________
InCase(ByRef CaseCommand) {
; This function handles a variety of conditional commands that specify the case in which they apply, and what change to make.
; The commands are made up of one or more clauses that contain regular expressions, maps, and literal text.
; The following clause combinations are valid: (Square brackets indicates optional clause.)
; Map(M) [ elseSend(T2) ]
; thenSend(T1)
; After(RE) thenSend(T1) [ elseSend(T2) ]
; [ After(LB) ] Replace(RE) with(BT) [ elseSend(T2) ]
; [ After(LB) ] Replace(F) with(R) usingMap(M) [ elseSend(T2) ]
; The parameters to these clauses are:
; M: One or more mapping strings containing sequences of literal text. See Map() clause for details.
; RE: A regular expression
; T1, T2: Literal text to send
; LB: Positive look-behind regular expression. May contains wildcards * and +.
; BT: Literal text to send, but it may contain backreferences as $0 (whole matched expression), $1 (matched group 1), etc.
; F: Regex to find. Must contain the symbol $F to indicate the position of the literal map-FROM text.
; R: Replacement expression. Must contain $R to indicated the positon of the literal map-TO text. May also contain backreferences such as $1.
if (InStr("MT", SubStr(CaseCommand,1,1)) ) {
return InRota(CaseCommand)
}
;~ if (SubStr(CaseCommand,1,1) = "D") {
;~ sub := SubStr(CaseCommand, 3, -1)
;~ gosub %sub%
;~ return 1
;~ }
x := SendData(0x9020, CaseCommand)
if (x < 2 or x > 3)
outputdebug ** ERROR: InCase() error code %x%
return (x = 2 ? 1 : 0)
}
Map(MapStringList*) {
; A Map is comprised of one or more mapping strings.
; Example with a single mapping string: Map("n ɲ ŋ")
; Example with multiple mapping strings: Map("a ɐ", "e ə", "i ɩ", "o ø", "u ʌ ʊ")
;
; Each mapping string contains a set of two or more segments of literal text, mapping from left to right.
; The segments are separated by the single arrow ( ) or triple arrow (⇛) characters.
; A single arrow separator indicates a normal mapping.
; A triple arrow separator indicates a mapping that applies only in the case of a multi-tap (i.e. rapid) key sequence.
; e.g. Map("n⇛ɲ⇛ŋ")
; Note that in the current implementation, the mapping between all segments in all mapping strings should use the same
; type of mapping: normal ( ) or multi-tap (⇛). At present, if any multi-tap arrows are present in any string, all mappings in all strings
; of that map will be treated as multi-tap. In the future, this may change.
;
; If the mapping string is terminated with looping arrow (↺) character, the final segment maps back to the first segment.
; e.g. Map("a ɐ↺", "e ə↺", "i ɩ↺", "o ø↺", "u ʌ ʊ↺")
;
; If the map should produce the first segment even if nothing matches, begin the mapping string with an arrow.
; (Think of it as mapping from "nothing" to the "default segment".)
; e.g. Map(" n⇛ɲ⇛ŋ") -A single tap produces "n"; a double tap produces "ɲ", and a triple tap produces "ŋ".
; If the map has multiple mapping strings, this is valid only on the FIRST mapping string.
; It is identical in behavior to specifying an elseSend() clause, though an elseSend() clause is also free to send text
; not found in the mapping string.
;
; Segments are always literal text.
; The only exception is that characters may be represented by the notation "\x{H}", where H is the hexadecimal codepoint
; of any Unicode character, including SMP characters.
; e.g. Map(" \x{301} \x{300} \x{302}↺")
; The above example produces diacritic U+0301 by default, cycling that through the other diacritics, and then looping back to
; diacritic U+0301 again. Advanced Note: This map produces a rotation of three states. If you want the fourth state of there being
; no diacritic in the rotation, then add another arrow to "nothing" after the final segment, like this:
; e.g. Map(" \x{301} \x{300} \x{302} ↺")
;
; Note that the same segment may appear in more than one mapping string in a map. In this case, whichever mapping string was
; in use on the prior keystroke will be used again. For example, the IPA keyboard uses the equals [=] key to cycle between various
; forms of the prior letter. Here is a simplified extract of that map for the h-like and the y-like letters.
; Note that the character "ɥ" appears in both mapping stringsː
; Map("h ɥ ħ ɦ↺", "y ɥ ʏ↺")
; If the user was using this map to cycle through forms of "h", then "ɥ" maps to "ħ".
; If he was using it to cycle through forms of "y", then "ɥ" maps to "ʏ".
; Caveat: In the current version, if one segment is identical to the final portion of another segment, the longer segment should
; be placed earlier in the string. For example, in the IPA keyboard, repeated presses on the colon key produce this sequence:
; ː ˑ ːː (then it loops back to the start)
; So we use elseSend() to specify the default sequence, and reorder the string to put the longer one earlier:
; $+;::InCase(Map("ːː ː ˑ↺") elseSend("ː"))
str := ""
for index,param in MapStringList
str .= param chr(0x13)
return "M:" str chr(0x1c)
}
LoopMap(MapStringList*) {
; Use in place of Map() to indicate that every mapping string is looping.
str := ""
for index,param in MapStringList
str .= param chr(0x14) chr(0x13)
return "M:" str chr(0x1c)
}
MultiTapMap(MapStringList*) {
; Use in place of Map() to indicate that the mapping applies only in the event of multi-tap timing.
; Principle for good keyboard design: Avoid requiring more than a triple tap.
str := ""
for index,param in MapStringList
str .= param chr(0x13)
return "T:" str chr(0x1c)
}
MultiTapSend(MapStringList*) {
; Use in place of MultiTapMap() to indicate that the first element in the map is the default text to send.
str := " "
for index,param in MapStringList
str .= param chr(0x13)
return "T:" str chr(0x1c)
}
After(RE) {
return "A:" RE chr(0x1c)
}
thenSend(T1, uFlags=0) {
return "S:" T1 chr(0x1c) "Sf:" uFlags chr(0x1c)
}
Replace(RE) {
return "R:" RE chr(0x1c)
}
with(BT, uFlags=0) {
return "W:" BT chr(0x1c) "Wf:" uFlags chr(0x1c)
}
usingMap(rotaSets*) { ; e.g. usingMap("n ɲ ŋ", "N ɴ Ŋ")
; Differences from the Map() clause:
; -You may not use an initial arrow as Map() does to specify a default character.
; -Avoid using maps that contain the same segment in more than one mapping string,
; as you cannot depend on which one will be matched with the usingMap() clause.
; -The multi-tap arrow (⇛) currently behaves the same as a normal arrow ( ).
; This may change in future versions. For now, if you want multi-tap behavior,
; use the MultiTap() function. e.g. MultiTap() and InCase(...)
str := ""
for index,param in rotaSets
str .= param chr(0x13)
return "U:" str chr(0x1c)
}
usingLoopMap(rotaSets*) { ; e.g. usingMap("n ɲ ŋ↺", "N ɴ Ŋ↺")
; Differences from the Map() clause:
; -You may not use an initial arrow as Map() does to specify a default character.
; -Avoid using maps that contain the same segment in more than one mapping string,
; as you cannot depend on which one will be matched with the usingMap() clause.
; -The multi-tap arrow (⇛) currently behaves the same as a normal arrow ( ).
; This may change in future versions. For now, if you want multi-tap behavior,
; use the MultiTap() function. e.g. MultiTap() and InCase(...)
str := ""
for index,param in rotaSets
str .= param chr(0x12) chr(0x13)
return "U:" str chr(0x1c)
}
elseSend(T2, uFlags=0) {
return "E:" T2 chr(0x1c) "Ef:" uFlags chr(0x1c)
}
;_________________________ On Screen Keyboard Functions __________
; 0x1f divides K_OnScreenDef into separate screenitems
; 0x1e divides a screenitem into components. one such component is a cases string.
; 0x1d divides cases strings into separate case items.
; 0x1c divides case items into clauses
; 0x13 divides map clauses into map strings.
OnScreen(OnScreenItems*) {
; Defines the elements for an on-screen keyboard.
; These items can be calls to the following functions:
; Button()
; OnScreenCmd()
global K_OnScreenDef
str := ""
for index,param in OnScreenItems
str .= param chr(0x1f)
K_OnScreenDef := SubStr(str, 1, -1)
;~ OutputDebug K_OnScreenDef = "%K_OnScreenDef%"
}
Button(Label, Tip, Options, ActionCases) {
; Adds a button to the on-screen keyboard GUI.
; Parameters:
; Label: Text to appear on the button
; Tip: Help text to appear on mouse-over, to teach keystroke(s)
; Options: Position and size of button. See GUI command in AHK help file for details.
; ActionCases: A case command (as would be passed to InCase()) or a set of alternative case commands
; grouped using the Cases() function.
return "B:" Label chr(0x1e) Tip chr(0x1e) Options chr(0x1e) ActionCases chr(0x1e)
}
OnScreenCmd(cmd, param2="", param3="", param4="") {
; Causes an arbitrary GUI command to be performed, such as to set the font name, size, etc.,
; or to add non-button elements, such as Tab2 page controls.
; See AHK Help file for details.
return "C:" cmd chr(0x1e) param2 chr(0x1e) param3 chr(0x1e) param4 chr(0x1e)
}
Cases(CaseStrings*) {
; Use this to concatenate multiple Cases into a Cases parameter, as used by the Button() function.
str := ""
for index,param in CaseStrings
str .= param chr(0x1d)
return SubStr(str, 1, -1)
}
;~ DoHotkey(str) {
;~ ; InCase() clause that causes a certain hotkey's functionality to be performed.
;~ ; Use only when normal InCase() commands cannot handle the need.
;~ ; May not be compatible with TINKER.
;~ return "D:" chr(0x1c) str chr(0x1c)
;~ }
DeadKey(n=1) {
if (n<1 or n>8)
FatalError("DeadKey number must be between 1 and 8")
return chr(n)
}
; ___________ Phase and Deadkey ______________________________
; The difference between a Phase and a Deadkey is that pressing Backspace after a deadkey will
; consume that backspace in order to clear the deadkey without deleting the most recent real character.
; Pressing backspace when only a Phase is set will clear the Phase and delete the most recent real character.
; Generally, set a Deadkey when that is the only effect that the keystroke had. Set a phase when you want to
; establish a mode in combination with making some other text change. (e.g. via a Rota or Send)
SetPhase(n=1) {
; Sets a Phase that will be cleared (reset to 0) the next time characters are sent (or removed).
; Also cleared whenever the context is lost, such as due to navigation keys, mouse clicks, etc.
; The Phase can be any 32-bit number.
SendToInkey(0x8021, n)
return 1
}
IsPhase(n=1) {
return SendToInkey(0x8022, n)
}
GetPhase() {
return SendToInkey(0x8023)
}
MultiTap() {
; Returns true if this tap is a 2nd, 3rd, 4th, etc. rapid tap of the same key.
; e.g. MultiTap() and InCase(Map("ि ी", "इ ई"))
; This example is equivalent to: nCase(Map("ि ी", "इ ई"))
return (A_ThisHotkey = A_PriorHotkey and A_TimeSincePriorHotkey < 400 and A_TimeSincePriorHotkey > 0) ;TODO: Use user parameter, not hard-coded 400 ms
}
Beep(n="*-1") {
; Useful signal if a key's function does not apply, so no change is being made.
; e.g. InRota({}, "n ɲ ŋ", "N ɴ Ŋ") or Beep()
SoundPlay %n%
return 1
}
; Retrieves the value of a named option (as parsed from the K_Params global variable).
; This assumes that K_Params string is comprised of one or more paramName=paramValue elements,
; and that the values are single words not containing whitespace characters.
; If you use parameters differently, parse K_Params directly as appropriate.
Option(optionName, defaultVal = 0) {
global K_Params
if RegExMatch(K_Params, "i)(?<=\b" . optionName . "=)\S+", v)
return v
return defaultVal
}
UseUpperCase(){
; Returns True if the upper-case version of a character should be output, based on both Shift and CAPS statuses.
return GetKeyState("Shift") ^ GetKeyState("CapsLock", "T")
}
currCh() {
; Returns the character that would normally be produced by A_ThisHotKey.
; For the sake of efficiency, this assumes that it is being used in response to a hotkey for which
; the last character of the hotkey name is the character to be produced.
; Thus, you should not use this function within a handler for a hotkey name of $+; which typically captures the colon key,
; The character is uppercased based on the current state of the Shift key
; (and, if K_UseContext=2, on the state of the CAPS key).
global K_UseContext
k := SubStr(A_ThisHotKey, 0)
if ((K_UseContext = 2) ? UseUpperCase() : GetKeyState("Shift") )
StringUpper k, k
else
StringLower k, k
return k
}
currDigit() {
; Returns the numeric value of the digit when A_ThisHotKey is a digit key.
; Result is unpredicible if called for other hotkeys
return Asc(SubStr(A_ThisHotKey, 0)) & 0xF
}
TrayTipQ(txt, title="", ms=2000) {
; Show a tip at the tray icon
s := txt . "|" . title . "|" . ms
SendData(0x9001, s)
return 1
}
Char(codePt) {
; Use this instead of AHK's Char() function if you need to handle SMP characters too.
if (codePt > 0xffffffff) ; Invalid Unicode
return ""
if (codePt & 0xffff0000) { ; SMP character. Convert to surrogate pair
codePt -= 0x10000
return Chr((0xd800 | (codePt >> 10))) . chr((0xdc00 | (codePt & 0x3ff)))
}
return chr(codePt) ; BMP character.
}
Ord(str) {
; Use this instead of AHK's Asc() function if you need to handle SMP characters too.
if (str = "")
return 0
o := Asc(Substr(str,1,1))
if (o & 0xd800 = 0xd800) { ; if SMP
o2 := Asc(Substr(str, 2, 1))
return (((o & 0x3ff) << 10) | (o2 & 0x3ff)) + 0x10000
}
return Asc(str)
}
FatalError(str) {
; Terminate this keyboard due to an error condition. (If you merely do ExitApp, InKey may keep trying to run this script.)
s = %str%`nTerminating %A_ScriptName%
SendData(0x9006, s)
ExitApp
}
Context(Length) {
; Retrieve the context string, up to the specified length (measured in UTF-16 code units).
varsetcapacity(buf, (Length+1)*2, 0)
ct := 0
loop %length% {
cc := ctx(A_Index)
if (cc = "")
break
NumPut(cc, buf, (Length-A_Index)*2, "UShort")
ct++
}
return StrGet(&buf + (length - ct)*2, ct)
}
; _____________________________________________________ No need to call these directly if you use K_UseContext = 1 or 2
UndoLast() {
; Call this when BackSpace is tapped.
SendToInKey(0x8015)
return 1
}
SendBackspace() {
; Call this (perhaps for Shift Backspace?) to just delete without replacing intermediate stages. Clear the undo history for keystrokes.
SendToInKey(0x8019)
return 1
}
DoSpace() {
; chk()
; Call this when the spacebar is pressed.
SendToInKey(0x8018)
return 1
}
DoTab() {
; Call this when the Tab key is pressed.
SendToInKey(0x8017)
return 1
}
DoEnter() {
; Call this when the Enter key is pressed.
SendToInKey(0x8016)
return 1
}
Back(ct=1) {
; chk()
; Back up a specified number of characters
SendToInKey(0x8013, ct)
return 1
}
; ------------------------------------------------------------------ The functions beyond this point should not be used in new keyboards. Just for backwards compatibility.
SetDeadkey(n=1) {
; Sets a Deadkey that will be cleared (reset to 0) the next time characters are sent (or removed).
; Also cleared whenever the context is lost, such as due to navigation keys, mouse clicks, etc.
; The Deadkey can be any 32-bit number.
; USING DEADKEYS IS USUALLY POOR KEYBOARD DESIGN. AIM TO PROVIDE A MORE INTELLIGENT ALTERNATIVE.
SendToInkey(0x8031, n)
return 1
}
IsDeadkey(n=1) {
return SendToInkey(0x8032, n)
}
GetDeadkey() {
return SendToInkey(0x8033)
}
SendChar(cp, uFlags=0) {
; Send a character of the given codepoint (up to 0x10FFFF), optionally remembering a 32-bit flags value associated with it.
; While this is theoretically more efficient than using Send(), the savings only amounts to about 0.2 milliseconds,
; so use whichever makes the keyboard easier to manage.
if (cp & 0xf800 = 0xd800)
OutputDebug This keyboard uses SendChar with surrogate code units. Correct it to use actual codepoint.
ok := SendToInKey(0x8010, cp, uFlags)
if (ok)
return
MsgBox Communication with InKey has been lost.`nPlease restart it.
ExitApp
}
RegisterVirtualKeyboardHwnd(hwnd) {
SendToInKey(0x8019)
return 1
}
ctx(pos = 1)
; DEPRECATED. Use InCase() instead.
; Get the value of a character at a specified position back in the context.
; e.g. ctx(2) returns the value of the 2nd-most-recent character of the context.
{
x := SendToInkey(0x8011, pos)
if (x = 0)
return ""
return x
}
flags(pos = 1)
; Get the flags at a specified position back in the context.
; e.g. flags(2) returns the flags for the the 2nd-most-recent character of the context.
{
x := SendToInkey(0x8012, pos)
if x = 0
return ""
return x
}
SendChars(ChList, uFlags=0) {
; Deprecated. Only for backwards compatibility. Use Send() instead. This function will disappear in future versions.
;
; Send a comma-delimited set of 16-bit codepoints, optionally remembering on each the same 32-bit flag value.
; Less efficient than SendChar or Send.
; e.g. SendChars("0x926,0x94D,0x935", 1)
VarSetCapacity(v, 256, 0)
; struct: UInt uFlags
; UShort numChars
; UShort[] charArray
NumPut(uFlags, v, 0, "UInt") ; 32-bit
numCh := 0
Loop parse, ChList, CSV
NumPut(A_LoopField, v, (2 * numCh++) + 6, "UShort")
NumPut(numCh, v, 4, "UShort")
SendData(0x900A, v, 0, numCh*2 + 6)
return 1
}
DeleteChar(numCharsToDel=1, endingPos=1) {
; DEPRECATED. Use InCase() instead
; DeleteChar(1,1) is virtually equivalent to a backspace.
; DeleteChar(2, 3) when context is "abXXcd" will delete the 2 X'es that end 3 characters back.
;~ SendToInKey(0x8014, numCharsToDel, endingPos)
setformat integerfast, D
n := numCharsToDel + 0
e := endingPos - 1
return InCase(Replace(".{" n "}(.{" e "}") with("$1"))
}
ReplaceChar(cp, numCharsToRep=1, endingPos=1, uFlags=0) {
; DEPRECATED. Use InCase() instead
; Replace one or more characters with the character of the specified codepoint
; The characters to replace are identified by their count and their ending position.
; e.g. If the context is "abXXde", then ReplaceChar(42, 2, 3) will make it "ab*de".
;~ s := cp . "," . numCharsToRep . "," . endingPos . "," . uFlags
;~ SendData(0x9002, s)
;~ return 1
setformat integerfast, H
cp += 0
cpstr := substr(cp, 3)
setformat integerfast, D
n := numCharsToRep + 0
e := endingPos - 1
return InCase(Replace(".{" n "}(.){" e "}") with("\x{" cpstr "}$1", uFlags))
}
InsertChar(u, uFlags=0) {
; DEPRECATED. Use InCase() instead
; Insert a character (whose code is u) immediately prior to the most recent character.
OutputDebug This keyboard uses the old InsertChar() function. It should be updated to use InCase() instead.
return InCase(Replace(".") with(Char(u) "$0", uFlags))
;~ OutputDebug %s%
;~ s := u . "," . uFlags
;~ SendData(0x9003, s)
;~ return 1
}
InsertChars(ChList, uFlags=0, pos=1) {
; DEPRECATED. Use InCase() instead
; Insert characters (whose codes are specified in a tab-delimited string ChList) at pos number of characters back in the context.
; e.g. InsertChars("42,43", uFlags, 1) would insert characters 42 and 43 immediately prior to the most recent character.
;~ VarSetCapacity(v, 256, 0)
; struct: UInt uFlags
; UShort pos
; UShort numChars
; UShort[] charArray
;~ NumPut(uFlags, v, 0, "UInt") ; "UInt" - 32 bits
;~ NumPut(pos, v, 4, "UShort") ; "UShort" - 16 bits
;~ numCh := 0
;~ Loop parse, ChList, CSV
;~ NumPut(A_LoopField, v, (2 * numCh++) + 8, "UShort")
;~ NumPut(numCh, v, 6, "UShort")
;~ SendData(0x900B, v, 0, numCh*2 + 8)
;~ return 1
OutputDebug This keyboard uses the old InsertChar() function. It should be updated to use InCase() instead.
VarSetCapacity(v, strlen(chlist)*2, 0)
numCh := 0
Loop parse, ChList, CSV
NumPut(A_LoopField, v, (2 * numCh++), "UShort")
str := StrGet(&v)
return InsertText(str, uFlags, pos)
}
InsertText(ByRef CharString, uFlags=0, pos=1) {
; DEPRECATED. Use InCase() instead
; Insert a text string at pos number of characters back in the context.
; e.g. InsertChars("Ba", uFlags, 1) would insert "Ba" immediately prior to the most recent character.
; Optionally remembers on each character the same 32-bit flag value.
setformat integerfast, D
p := pos + 0
return InCase(Replace(".{" p "}") with("\Q" char(cp) "\E$0", uFlags))
;~ len := StrLen(CharString)
;~ size := len*2+10
;~ VarSetCapacity(v, size, 0)
;~ ; struct: UInt uFlags
;~ ; UShort pos
;~ ; UShort numChars
;~ ; UShort[] charArray
;~ NumPut(uFlags, v, 0, "UInt") ; "UInt" - 32 bits
;~ NumPut(pos, v, 4, "UShort") ; "UShort" - 16 bits
;~ NumPut(len, v, 6, "UShort")
;~ StrPut(CharString, &v+8, size, "UTF-16")
;~ SendData(0x900B, v, pos, size)
;~ return 1
}
PreviewChar(cp, ms=0) {
; Show a character specified by the given codepoint (up to U+FFFF) in the preview tip window.
; If time is not specified, it will use InKey's SpeedRotaPeriod setting.
s := cp . "|" . ms
SendData(0x9008, s)
return 1
}
ToolTipU(txt, ms=0) {
; Show text in the preview tip window
; If time is not specified, it will use InKey's SpeedRotaPeriod setting.
s := txt . "|" . ms
SendData(0x9007, s)
return 1
}
RotaSets(rotaSets*) {
; Joins string parameters into a string delimited with newline characters.
; Useful for creating the rota list parameter for CreateRota/RegisterRota. (Eliminates the need for single-line rotas.)
; e.g. CreateRota(1, RotaSets("d ð ɖ ᶑ", "t θ ʇ ƭ ʈ", "T ʇ"), "=")
str := ""
for index,param in rotaSets
str .= param . chr(10)
return SubStr(str, 1, -1)
}
CreateRota(id, rotaSets, defTxt="", style=0, uFlags=0) {
; Register a rotation sequence. (See RegisterRota for comments on changes in parameters.)
; The parameters are:
; id - A short alphanumeric string that uniquely identifies this rotation sequence within the script.
; Valid characters are a-z and 0-9. If only digits are used, the ID does not need to be in quote marks.
; Calling RegisterRota() again with the same ID will replace the previous version.
; rotaSets - Unicode string containing one or more sets of strings/characters to cycle through for replacement.
; Each set will be cycled through independently of the other sets.
; > The sets are separated from each other by newline characters (i.e. a character 10).
; > Within each set, items are separated by a space.
; > If the last item in a set is separated by a tab instead of a space, that set will not loop back.
; defTxt - Unicode text to send if no match. If empty, no character will be sent.
; style - Styles (if not incompatible) can be combined by adding. Styles implemented so far:
; 1 DEPRECATED: Single-Line. A tab in the rota string marks the start of a new set in place of newline (chr(10)).
; Use RotaSets() instead.
; 2 DEPRECATED: Non-Looping. Rota will not loop back to the beginning of any set.
; Use this only for single-line rotas, where non-looping cannot be indicated with tabs.
; 8 Expiring rota. Rota string will not be searched for a match if the previous call to do this rota
; was more than a user-defined number of milliseconds ago, or if the flags on the last character in
; the context differ from this rota's uFlags parameter.
; For this style to be useful, either <def> must match a character in the rota, or <def> must be 0
; and the keyboard script must follow up with an appropriate SendChar with the same flags as <uFlags>.
; 16 Preview. Show a preview of the rota's next character in a tool-tip.
; uFlags - Flag value to set on any character that is sent via this rota (including the default character).
; See also comments on style 8 for Expiring rotas.
; Remarks:
; The rota string is searched from left to right for the first match. Thus, a string of "cat" would typically need to be to the
; left of "at". The exception to this is that if the rota has just been performed when it is called again, matching will
; begin at the beginning of the set in which a match was most-recently found. Thus, multiple sets in a rota list may
; contain the same character or string. e.g.
; rota = blue orange white%A_Tab%apple orange pear
; RegisterRota("eg", rota, 0, 0, 0, 1)
; When DoRota("ex") is called, if the last thing typed is "blue", it will be replaced with "orange". If called again,
; that will be replaced with "white", and then back to "blue". However, if the last thing typed is "apple", that will
; be replaced by "orange", which will this time be replaced by "pear", not "white", since we are cycling through the
; second set this time.
;
;~ if RegExMatch(id, "\W") {
;~ FatalError("Error: Invalid ID " . id . " in call to RegisterRota")
;~ }
;~ if (RegExMatch(rotaSets, "\s\s")) {
;~ FatalError("Error: Double whitespace in rota string " . id)
;~ }
;~ s := K_FileID . "_" . id . "|" . def . "|" . uFlags . "|" . back . "|" . style . "|" . rotaSets
;~ SendData(0x9004, s)
global
if not RegExMatch(id, "^\w+$") {
FatalError("Error: Invalid ID " . id . " in call to RegisterRota")
}
if (RegExMatch(rotaSets, "\s\s")) {
FatalError("Error: Double whitespace in rota string " . id)
}
SendData(0x9004, K_FileID . "_" . id . chr(0x1c) . rotaSets . chr(0x1c) . defTxt . chr(0x1c) . style . chr(0x1c) . uFlags)
K_RegRotas.Insert(id, 1)
;~ outputdebug % ">>>>>>>>>>create rota " id ". k_regrotas[id]=" K_RegRotas[id]
}
RegisterRota(id, rotaSets, defChr=0, uFlags=0, back=0, style=0) {
; DEPRECATED. Use InRota() or CreateRota() instead
; Older version of CreateRota.
; Differences to note:
; defChr parameter took a numeric codepoint (up to 0xFFFF) rather than text.
; Parameters were in a different order.
; Back parameter is no longer used in CreateRota.
; To translate old scripts: RegisterRota(ID, RotaSets, A, B, C, D) -> CreateRota(ID, RotaSets, Asc(A), D, B)
return CreateRota(id, rotaSets, Chr(defChr), style, uFlags)
}
;~ DoRota(id, rotaSets="", defTxt="", style=0, uFlags=0) {
DoRota(id) {
global
; chk()
; Perform a rotation sequence, optionally registering a rota if it has not already been registered.
; Parameters:
; id - Identifies a sequence already registered by RegisterRota()
; Return Value: Currently, 1 if a match was found. 0 otherwise. (This may change.)
;~ if (not K_RegRotas.HasKey(id)) {
;~ CreateRota(id, rotaSets, defTxt, style, uFlags)
;~ }
local s := K_FileID . "_" . id
local x := SendData(0x9005, s)
outputdebug DoRota(%id%) [%s%] => %x%
if (x = 4)
MsgBox 16, , DoRota(%id%)`nNo such rota exists
if (x < 2 or x > 3)
outputdebug ** ERROR: DoRota error code %x%
return (x = 2 ? 1 : 0)
}
; Retrieves the value of a named parameter (as parsed from the K_Params global variable).
; This assumes that K_Params string is comprised of one or more paramName=paramValue elements,
; and that the values are single words not containing whitespace characters.
; If you use parameters differently, parse K_Params directly as appropriate.
GetParam(paramName, defaultVal = 0) {
global K_Params
if RegExMatch(K_Params, "i)(?<=\b" . paramName . "=)\S+", v)
return v
return defaultVal
}
SendRotaChar(id, u, uFlags) {
; For a rota that has no default char, instead of following DoRota() with SendChar(), follow it with SendRotaChar().
; This is particularly important for a rota with the Preview style, so that the preview can be updated.
global K_FileID
if (not (id and u)) {
outputdebug **** ERROR: SendRotaChar(%id%, %u%, %uFlags%)
return
}
s := K_FileID . "_" . id . "|" . u . "|" . uFlags
outputdebug Sending SendRotaChar message: %s%
x := SendData(0x9009, s)
return (x = 2 ? 1 : 0)
}
;~ InContextSend(FindRegEx, SendTxt, ElseTxt="", uSendFlags=0, uElseFlags=0) {
;~ chk()
;~ ; If the context matches the FindRegEx pattern, send the Send string to the application, optionally with the specified uSendFlags.
;~ ; Otherwise, if ElseText is specified, send that (optionally with the specified uElseFlags).
;~ ; The end of the FindRegEx pattern is implicitly anchored to the end of the preceding context.
;~ ; Return value is non-zero if there was a match, so if no ElseText was specified, multiple function calls can be chained together with 'or'.
;~ ; Example:
;~ ; $i::InContextSend("[क-हक़-य़]\x{93c}?", "ि") or DoRota(37)
;~ ; Experimental. Subject to change. Not very efficient yet.
;~ x := SendData(0x9015, FindRegEx . chr(0x1c) . SendTxt . chr(0x1c) . ElseTxt . chr(0x1c) . uSendFlags . chr(0x1c) . uElseFlags)
;~ if (x < 2 or x > 3)
;~ outputdebug ** ERROR: InContextSend error code %x%
;~ return (x = 2 ? 1 : 0)
;~ }
;~ InLBContextReplace(LookBehind, FindRegEx, ReplaceTxt, ElseTxt="", uSendFlags=0, uElseFlags=0) {
;~ ; If the context matches the FindRegEx pattern (which follows the optional LookBehind regex), replace the matched characters with the ReplaceTxt text, optionally with the specified uSendFlags.
;~ ; ReplaceTxt is plain text, not a regular expression, but it may contain backreferences such as $1 or ${1}. See RegExReplace documentation in AHK Help.
;~ ; Otherwise, if ElseText is specified, send that (optionally with the specified uElseFlags).
;~ ; The end of the FindRegEx pattern is implicitly anchored to the end of the preceding context.
;~ ; The LookBehind regex may contain variable-length quantifiers such as * and +. (This is not the case in AHK's (?<=...) look-behind syntax.)
;~ ; Return value is non-zero if there was a match, so if no ElseText was specified, multiple function calls can be chained together with 'or'.
;~ ; Example:
;~ ; $+r::InContextReplace("", "([क-हक़-य़]\x{93c}?)", "र्$1") or DoRota(37)
;~ ; Experimental. Subject to change.
;~ x := SendData(0x9016, LookBehind . chr(0x1c) . FindRegEx . chr(0x1c) . ReplaceTxt . chr(0x1c) . ElseTxt . chr(0x1c) . uSendFlags . chr(0x1c) . uElseFlags)
;~ if (x < 2 or x > 3)
;~ outputdebug ** ERROR: InContextReplace error code %x%
;~ return (x = 2 ? 1 : 0)
;~ }
;~ InContextReplace(FindRegEx, ReplaceTxt, ElseTxt="", uSendFlags=0, uElseFlags=0) {
;~ ; as above, but without LookBehind parameter
;~ x := SendData(0x9016, chr(0x1c) . FindRegEx . chr(0x1c) . ReplaceTxt . chr(0x1c) . ElseTxt . chr(0x1c) . uSendFlags . chr(0x1c) . uElseFlags)
;~ if (x < 2 or x > 3)
;~ outputdebug ** ERROR: InContextReplace error code %x%
;~ return (x = 2 ? 1 : 0)
;~ }
SendText(ByRef CharString, uFlags=0) {
; Send a text string
; e.g. Send("द्व")
SendData(0x901A, CharString, uFlags)
return 1
}
;=======================================
; Functions after this point are internal, and should not be directly called by keyboard scripts.
; chk() {
; OutputDebug A_ThisHotkey=%A_ThisHotkey%, A_PriorHotkey=%A_PriorHotkey%, A_PriorKey=%A_PriorKey%
; }
; array := ComObjCreate("Scripting.Dictionary")
; array.item("A") := "À"
; , array.item("a") := "à"
; , array.item("e") := "è"
; , array.item("E") := "È"
; For key in array
; MsgBox % key " => " array.item(key)
InRota(MapCmd) {
static ct := 1
static K_InRotas := ComObjCreate("Scripting.Dictionary")
global
; chk()
local str, index, param, rotaStr, rotaID
rotaID := K_InRotas.item(MapCmd)
if (not rotaID) {
rotaID := K_FileID "i" ct++
SendData(0x9024, rotaID chr(0x1c) MapCmd) ; Register this rota now
K_InRotas.item(MapCmd) := rotaID
}
local x := SendData(0x9005, rotaID)
outputdebug InRota() [%rotaID%] => %x%
if (x < 2 or x > 3)
outputdebug ** ERROR: InRota error code %x%
return (x = 2 ? 1 : 0)
}
;~ OldInRota(props, rotaSets*) {
;~ ; A rota is a means of mapping text strings (often just a single character) to some other replacement text string. e.g. A simple rota is: "n ŋ"
;~ ; A rota can contain multiple sets, and each set can map a sequence of replacements. e.g. "n ɲ ŋ", "N ɴ Ŋ"
;~ ; Each set can be looping or non-looping. This is determined (for now) by whether the last item in the series is separated by a TAB instead of a space, which is otherwise the delimiter.
;~ ; Parameters:
;~ ; props: an object that may contain optional parameters: (subject to change! For future compatibility, only use lowercase names)
;~ ; def: Default text to send if no match is made
;~ ; style: Numeric style same as RegisterRota parameter. Currently, the only relevant style might be 8 for expiring rota, but better to use MultiTap() for that.
;~ ; flags: Flags to store in history for any text sent by this rota
;~ ; Return Value: 1 if rota did something. 0 if no match found and no default text provided.
;~ ; Examples:
;~ ; $=::InRota({def: "="}, "n ɲ ŋ", "N ɴ Ŋ")
;~ ; $=::InRota("n ɲ ŋ", "N ɴ Ŋ") or Beep()
;~ static ct := 1
;~ static K_InRotas := object()
;~ global
;~ chk()
;~ local str, index, param, rotaStr, rotaID
;~ str := ""
;~ for index,param in rotaSets
;~ str .= param . chr(10)
;~ rotaStr := SubStr(str, 1, -1) chr(0x1c) props["def"] chr(0x1c) props["style"] chr(0x1c) props["flags"]
;~ rotaID := K_InRotas[rotaStr]
;~ if (not rotaID) {
;~ rotaID := K_FileID "i" ct++
;~ SendData(0x9004, rotaID chr(0x1c) rotaStr) ; Register this rota now
;~ K_InRotas.Insert(rotaStr, rotaID)
;~ }
;~ local x := SendData(0x9005, rotaID)
;~ outputdebug InRota() [%rotaID%] => %x%
;~ if (x = 4)
;~ MsgBox 16, , InRota(%rotaID%)`nNo such rota exists
;~ if (x < 2 or x > 3)
;~ outputdebug ** ERROR: InRota error code %x%
;~ return (x = 2 ? 1 : 0)
;~ }
OnMsgSuspend() {
global
Suspend On
outputdebug suspending %A_ScriptName%
if (K_OnScreenDef and K_ScrKbdHwnd) {
WinClose ahk_id %K_ScrKbdHwnd%
K_ShowOnScreenAfterResume := 1
} else {
K_ShowOnScreenAfterResume := 0
}
return 3
}
OnMsgResume() {
global
Suspend Off
outputdebug resuming %A_ScriptName%
if (K_ShowOnScreenAfterResume) {
OnMsgOnScreen()
;~ OutputDebug K_ScrKbdHwnd=%K_ScrKbdHwnd%
return K_ScrKbdHwnd
}
return 3
}
OnMsgClose() {
Suspend Off
ExitApp
}
Receive_WM_COPYDATA(wParam, lParam)
{ ; This function can receive string data from the main InKey app.
; Any processing done in response must return quickly. If necessary, set a timer to kick off a longer process.
dwNum := NumGet(lParam+0, 0, "UPtr") ; specifying MyVar+0 forces the number in MyVar to be used instead of the address of MyVar itself
cbDataSize := NumGet(lParam + A_PtrSize, "UInt") ; address of CopyDataStruct's cbData member.
StringAddress := NumGet(lParam + A_PtrSize + 4, "UPtr") ; Retrieves the CopyDataStruct's lpData member.
if (cbDataSize = 0)
StringData := ""
else
{
StringData := StrGet(StringAddress) ; Copy the string out of the structure. Assumed to be null-terminated
; If the data cannot be assumed to contain no nulls except(and always) at the end of the string, then we can use memcpy instead:
;~ VarSetCapacity(StringData, dwSize, 0)
;~ DllCall("MSVCRT\memcpy", "str", StringData, "UPtr", StringAddress, "uint", cbDataSize) ; Copy the string out of the structure.
; Handle string passed as a Reinitialize command (0x9201)
if (dwNum = 0x9201 and RegExMatch(StringData,"^(?P<kbd>.*)\|(?P<par>.*)", K_ov_)) {
outputdebug received Reinitialize message: %K_ov_kbd%, %K_ov_par%
return Reinit(K_ov_kbd, K_ov_par)
; Sometimes Windows doesn't deliver the message, but returns 1 or 0. We will never return 1 or 0, so that that can be recognized as unprocessed.
}
}
setformat integerfast, H
dwNum += 0
Outputdebug ** ERROR: %A_ScriptName% has no handler for received string: %dwNum%, "%StringData%"
setformat integerfast, d
return 2 ; Tell sender that we didn't process this string
}
Reinit(kbd, par) {
global ; Must be global because we are GoSubbing to code expecting globals!
Suspend On
K_Params := par
K_ID := kbd
outputdebug Reinit(%kbd%, %par%) K_LoadedIDs=%K_LoadedIDs%
if (not InStr(K_LoadedIDs, ":" . K_ID . ":")) {
outputdebug K_LoadedIDs=%K_LoadedIDs% before
K_LoadedIDs .= K_ID . ":"
outputdebug K_LoadedIDs=%K_LoadedIDs% after
Label = OnLoadKeyboard
if (IsLabel(Label))
GoSub %Label%
}
Label = OnKeyboardInit
if (IsLabel(Label))
GoSub %Label%
if (K_ShowOnScreenAfterResume) {
OnMsgOnScreen()
OutputDebug K_ScrKbdHwnd=%K_ScrKbdHwnd%
Suspend Off
return K_ScrKbdHwnd
}
Suspend Off
return 3
}
; Receive_WM_COPYDATA(wParam, lParam)
; { ; ;This function can receive string data from the main InKey app.
; Any processing done in response must return quickly. If necessary, set a timer to kick off a longer process.
; global K_Params
;x := NumGet(lParam + 0) ; We aren't currently using the dwData element, but we could.
; outputdebug %A_ScriptName% received WM_COPYDATA
; StringAddress := NumGet(lParam + 8) ; lParam+8 is the address of CopyDataStruct's lpData member.
; StringLength := DllCall("lstrlen", UInt, StringAddress)
; if (StringLength <= 0)
; StringData := ""
; else
; {
; VarSetCapacity(StringData, StringLength)
; DllCall("lstrcpy", "str", StringData, "uint", StringAddress) ; Copy the string out of the structure.
; outputdebug %A_ScriptName% received string message: %StringData%
; Handle string passed as a reinitialization command
; if (SubStr(StringData,1,3) = "R: ") { ; If we receive a string beginning with "R: ", this is a parameter string for reinitialization
; Suspend Off
; K_Params := SubStr(StringData, 4)
; K_ID := 1
; GoSub OnLoadKeyboard
; GoSub OnKeyboardInit
; return 2 ; Sometimes Windows doesn't deliver the message, but returns 1. We will never return 1, so that that can be recognized as unprocessed.
; }
; }
; Outputdebug No handler for received string: >>%StringData%<<
; return false ; Tell sender that we didn't process this string
; }