forked from jeffpar/pcjs.v1
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkeyboard.js
1112 lines (1057 loc) · 53.4 KB
/
keyboard.js
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
/**
* @fileoverview This file implements the C1Pjs Keyboard component.
* @author <a href="mailto:[email protected]">Jeff Parsons</a>
* @copyright © 2012-2020 Jeff Parsons
*
* This file is part of PCjs, a computer emulation software project at <https://www.pcjs.org>.
*
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <https://www.pcjs.org/modules/shared/lib/defines.js>.
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of PCjs
* for purposes of the GNU General Public License, and the author does not claim any copyright
* as to their contents.
*/
"use strict";
if (typeof module !== "undefined") {
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/**
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
* which would force us to declare all class properties in the constructor, as well as prevent
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
*
* @unrestricted
*/
class C1PKeyboard extends Component {
/**
* C1PKeyboard(parmsKbd)
*
* The Keyboard component can be configured with the following (parmsKbd) properties:
*
* model: model number (one of: 542 or 600; 600 is the default)
*
* Its main purpose is to receive binding requests for various keyboard events,
* and to use those events to simulate the C1P's keyboard hardware; specifically,
* an OSI model 600 board (NOT the model 542).
*
* Keys on the C1P keyboard that differ from modern keyboards, along with their
* closest modern counterpart:
*
* C1P PC
* --- --
* 2" 2@
* 6& 6^
* 7' 7&
* 8( 8*
* 9) 9(
* 0 0)
* :* -_
* -= =+
* ;+ ;:
* LINEFEED CTRL-J
* RETURN ENTER (or CTRL-M)
* SHIFT-O DELETE (or CTRL-H)
* SHIFT-N ^
* SHIFT-P @ (used by the BASIC-IN-ROM to abandon the current line)
* SHIFT-L \
* SHIFT-K [
* SHIFT-M ]
* CTRL-C Same (used by the BASIC-IN-ROM to interrupt RUN and LIST, unless disabled with POKE 530,1)
* CTRL-O Same (used by the BASIC-IN-ROM to suppress output until another CTRL-O is typed)
* RUB-OUT (no mapping chosen for this key yet)
* REPEAT (no mapping chosen for this key yet)
*
* Problems with iOS Devices
* -------------------------
* The keyboard pops up with the SHIFT key depressed, which is not the initial keyboard state that the C1P expects.
* I tried to fix that by adding an 'autocapitalize="off"' attribute alongside the 'contenteditable="true"' attribute
* on the <canvas> element, but apparently Safari v5 honors that only inside certain elements (eg, <input>). The simplest
* work-around is to tap the iOS device's SHIFT key before starting to type, but I'd prefer an automatic solution.
*
* Another work-around might be to NEVER pass the real CAPS-LOCK state to the virtual machine, and whenever CAPS-LOCK is
* actually down, automatically "uncapitalize" all letters.
*
* However, the current work-around is for keyPressSimulate() to ALWAYS convert all alphabetic charCodes to their
* lower-case equivalents, and simply let the C1P's own shift-key logic do its thing. Note that we do this ONLY for
* iOS devices, so that on all other devices, you can still use special shifted key combinations like SHIFT-O and SHIFT-P;
* this work-around breaks those key combinations for iOS devices, but that seems like a reasonable trade-off.
*
* Simple C1P Virtual Hardware Test
* --------------------------------
* Using the following code:
*
* 10 POKE 530,1
* 20 KEY=57088
* 25 Q=-1
* 30 POKE KEY,64
* 40 P=PEEK(KEY)
* 50 IF P<>Q THEN PRINT P
* 60 Q=P
* 70 GOTO 30
*
* The value 64 (0x40) should have enabled every row except R6. Here were the results for keys
* on row R7:
*
* 1 2 3 4 5 6 7
* --- --- --- --- --- --- ---
* 126 190 222 238 246 250 252
*
* Sure enough, none of the keys on R6 worked, and all the keys on rows R0-R5 generated the same
* values as R7. But why were the values read all EVEN instead of ODD (ie, why was bit 0 cleared as
* well?) Answer: because 0x40 also enables keys on row R0, where the SHIFT-LOCK key resides,
* and since the SHIFT-LOCK is normally locked AND also sits in column C0, bit 0 will be clear as well.
*
* This explains why the "STAR WARS" game (SAMPLE4.BAS) expected values "126,190,222,238,246,250"
* for keys 1-6 after POKE'ing 64 into location 57088 (0xdf00), instead of the more typical values
* "127,191,223,239,247,251." This also means that anyone who happened to unlock their SHIFT-LOCK
* would have trouble playing that game.
*
* @this {C1PKeyboard}
* @param {Object} parmsKbd
*/
constructor(parmsKbd)
{
super("C1PKeyboard", parmsKbd);
this.flags.powered = false;
this.nDefaultModel = parmsKbd['model'];
/*
* keyCodes that I must pay particular attention to
*/
this.KEYCODE_DELETE = 0x08;
this.KEYCODE_TAB = 0x09;
this.KEYCODE_LF = 0x0A;
this.KEYCODE_CR = 0x0D;
this.KEYCODE_SHIFT = 0x10; // I map this to CHARCODE_LSHIFT
this.KEYCODE_CONTROL = 0x11;
this.KEYCODE_ALT = 0x12; // I map this to CHARCODE_RSHIFT (since the C1P keyboard has no ALT key)
this.KEYCODE_CAPSLOCK = 0x14;
this.KEYCODE_ESC = 0x1B; // NOTE: for some reason, this comes in via keyDown/keyUp only, not keyPress
this.KEYCODE_COMMAND = 0x5B;
/*
* The following charCodes are the same as the corresponding keyCodes
*/
this.CHARCODE_DELETE = this.KEYCODE_DELETE;
this.CHARCODE_LF = this.KEYCODE_LF;
this.CHARCODE_CR = this.KEYCODE_CR;
this.CHARCODE_ESC = this.KEYCODE_ESC;
/*
* The following charCodes are NOT the same as the corresponding keyCodes, hence the bias (PSEUDO_CHARCODE);
* I've deliberately chosen a bias that still produces values in the byte range (0x00-0xFF) and will therefore
* fit into aCharCodeMap, but which shouldn't conflict with any actual, type-able keys.
*/
this.PSEUDO_CHARCODE = 0xE0;
this.CHARCODE_LSHIFT = this.KEYCODE_SHIFT + this.PSEUDO_CHARCODE;
this.CHARCODE_CTRL = this.KEYCODE_CONTROL + this.PSEUDO_CHARCODE;
this.CHARCODE_RSHIFT = this.KEYCODE_ALT + this.PSEUDO_CHARCODE;
this.CHARCODE_SHIFTLOCK = this.KEYCODE_CAPSLOCK + this.PSEUDO_CHARCODE;
/*
* Other common character codes, pseudo (like the C1P's "BREAK" key, which has no modern analog) or otherwise
*/
this.CHARCODE_BREAK = 0x00 + this.PSEUDO_CHARCODE;
this.CHARCODE_CTRLC = 0x03;
this.CHARCODE_CTRLO = 0x0F;
/*
* These are "shift key" bits I store in bitsShift, and with the exception of BIT_COMMAND (because
* the C1P doesn't have a COMMAND key), they all match the bit position of the corresponding shift key's
* column on row 0 (abKbdCols[0]) of the simulated keyboard hardware.
*
* NOTE: Whenever shift key bits need to be restored from bitsShift to abKbdCols[0] (eg, when restoring
* the current shift state at the completion of a simulated key), be sure to mask bitsShift with BITS_SIMULATE
* before propagating them.
*/
this.BIT_SHIFTLOCK = 0x01;
this.BIT_RSHIFT = 0x02;
this.BIT_LSHIFT = 0x04;
this.BIT_COMMAND = 0x08; // the C1P has no key "wired" to this column, so I can use this bit for COMMAND
this.BIT_CTRL = 0x40;
this.BITS_SIMULATE = (this.BIT_RSHIFT | this.BIT_LSHIFT | this.BIT_CTRL);
this.SIMCODE_KEYPRESS = 0;
this.SIMCODE_KEYRELEASE = 1;
this.SIMCODE_KEYEVENT = 2;
this.SIMCODE_KEYTIMEOUT = 3;
this.SIMCODE_AUTOCLEAR = 4;
this.aSimCodeDescs = ["keyPress","keyRelease","keyEvent","keyTimeout","autoClear"];
/*
* From "OSI C1P Technical Report" p.19 regarding the Model 600 Board:
*
* "By holding down any key, one will first get one character output, and after approximately
* a half second delay a repeat rate of approximately 5 characters per second."
*/
this.nCyclesThreshold = 8192; // number of virtual CPU cycles required before aKbdStates is propagated
this.msReleaseDelay = 250; // number of milliseconds before a down key is "forced" up (unless we see it go up)
this.msReleaseRepeat = 100; // number of milliseconds before a held key is "forced" up (assuming auto-repeat)
this.msInjectDelay = 300; // number of milliseconds between injected keystrokes
this.aButtonCodeMap = {};
this.aButtonCodeMap['break'] = this.CHARCODE_BREAK;
this.aButtonCodeMap['esc'] = this.CHARCODE_ESC;
this.aButtonCodeMap['ctrl-c'] = this.CHARCODE_CTRLC;
this.aButtonCodeMap['ctrl-o'] = this.CHARCODE_CTRLO;
/*
* This array is used by keyEventSimulate() to lookup a given charCode and convert it to the appropriate
* row/col bit combination that the C1P requires. I assign each supported charCode a 16-bit value, where
* the high byte contains the row/col pair (in the high and low nibbles, respectively), and the low byte
* contains any required shift-key code.
*
* For example, an apostrophe (0x27) is an unshifted key on a modern keyboard, but it is a SHIFT-7 on the
* C1P keyboard, so when I simulate the 7, I must also simulate a SHIFT (I always choose the LEFT shift
* key -- CHARCODE_LSHIFT -- but that choice is completely arbitrary).
*
* Using charCodes (from keyPress events) proved to be more robust than using keyCodes (from keyDown and
* keyUp events), in part because of differences between the C1P keyboard's layout and modern keyboards,
* and also because of differences in the way browsers generate the keyDown and keyUp events. For
* example, Safari on iOS devices will not generate up/down events for shift keys, and for other keys,
* the up/down events are usually generated after the actual press is complete, and in rapid succession,
* which doesn't give the slow C1P virtual machine enough time to detect the key.
*
* There are still a few times that I call keyEventSimulate() from keyEvent(), and for those occasions,
* I create a pseudo-charCode value by adding PSEUDO_CHARCODE (0xE0) to the keyCode value, to avoid any
* confusion with real charCodes:
*
* CHARCODE_LSHIFT (originally 0x10, which also looks like CTRL-P, so converted to 0xF0)
* CHARCODE_CTRL (originally 0x11, which also looks like CTRL-Q, so converted to 0xF1)
* CHARCODE_RSHIFT (originally 0x12, which also looks like CTRL-R, so converted to 0xF2)
* CHARCODE_SHIFTLOCK (originally 0x14, which also looks like CTRL-T, so converted to 0xF4)
*
* Again, as things currently stand, iOS devices will never generate the above charCodes, so any C1P software
* that relies detecting on shift-key state changes will not work on those devices.
*
* For reference purposes, I've left some parenthetical references to corresponding keyCodes in the comments
* below. Relying on keyCodes is problematic, which is why I've tried to eliminate most dependencies on them,
* but still, they're all you get on keyDown/keyUp events.
*/
this.aCharCodeMap = [];
this.aCharCodeMap[0x31] = 0x7700; this.aCharCodeMap[0x21] = 0x7700 + this.CHARCODE_LSHIFT; // 1 (0x31) ! (0x31)
this.aCharCodeMap[0x32] = 0x7600; this.aCharCodeMap[0x22] = 0x7600 + this.CHARCODE_LSHIFT; // 2 (0x32) " (0xDE)
this.aCharCodeMap[0x33] = 0x7500; this.aCharCodeMap[0x23] = 0x7500 + this.CHARCODE_LSHIFT; // 3 (0x33) # (0x33)
this.aCharCodeMap[0x34] = 0x7400; this.aCharCodeMap[0x24] = 0x7400 + this.CHARCODE_LSHIFT; // 4 (0x34) $ (0x34)
this.aCharCodeMap[0x35] = 0x7300; this.aCharCodeMap[0x25] = 0x7300 + this.CHARCODE_LSHIFT; // 5 (0x35) % (0x35)
this.aCharCodeMap[0x36] = 0x7200; this.aCharCodeMap[0x26] = 0x7200 + this.CHARCODE_LSHIFT; // 6 (0x36) & (0x37)
this.aCharCodeMap[0x37] = 0x7100; this.aCharCodeMap[0x27] = 0x7100 + this.CHARCODE_LSHIFT; // 7 (0x37) ' (0xDE)
this.aCharCodeMap[0x38] = 0x6700; this.aCharCodeMap[0x28] = 0x6700 + this.CHARCODE_LSHIFT; // 8 (0x38) ( (0x39)
this.aCharCodeMap[0x39] = 0x6600; this.aCharCodeMap[0x29] = 0x6600 + this.CHARCODE_LSHIFT; // 9 (0x39) ) (0x30)
this.aCharCodeMap[0x30] = 0x6500; // 0
this.aCharCodeMap[0x3A] = 0x6400; this.aCharCodeMap[0x2A] = 0x6400 + this.CHARCODE_LSHIFT; // : (0xBA) * (0x38)
this.aCharCodeMap[0x2D] = 0x6300; this.aCharCodeMap[0x3D] = 0x6300 + this.CHARCODE_LSHIFT; // - (0xBD) = (0xBB)
//this.aCharCodeMap[0x00] = 0x6200; // RUB-OUT (no mapping chosen for this key yet)
this.aCharCodeMap[0x2E] = 0x5700; this.aCharCodeMap[0x3E] = 0x5700 + this.CHARCODE_LSHIFT; // . (0xBE) > (0xBE)
this.aCharCodeMap[0x6C] = 0x5600; this.aCharCodeMap[0x4C] = 0x5600 + this.CHARCODE_LSHIFT; this.aCharCodeMap[0x5C] = 0x5600 + this.CHARCODE_LSHIFT; // l L \
this.aCharCodeMap[0x6F] = 0x5500; this.aCharCodeMap[0x4F] = 0x5500 + this.CHARCODE_LSHIFT; this.aCharCodeMap[this.CHARCODE_DELETE] = 0x5500 + this.CHARCODE_LSHIFT; // o O DELETE
this.aCharCodeMap[this.CHARCODE_LF] = 0x5400; // LINE-FEED
this.aCharCodeMap[this.CHARCODE_CR] = 0x5300; // RETURN
this.aCharCodeMap[0x77] = 0x4700; this.aCharCodeMap[0x57] = 0x4700 + this.CHARCODE_LSHIFT; // w W
this.aCharCodeMap[0x65] = 0x4600; this.aCharCodeMap[0x45] = 0x4600 + this.CHARCODE_LSHIFT; // e E
this.aCharCodeMap[0x72] = 0x4500; this.aCharCodeMap[0x52] = 0x4500 + this.CHARCODE_LSHIFT; // r R
this.aCharCodeMap[0x74] = 0x4400; this.aCharCodeMap[0x54] = 0x4400 + this.CHARCODE_LSHIFT; // t T
this.aCharCodeMap[0x79] = 0x4300; this.aCharCodeMap[0x59] = 0x4300 + this.CHARCODE_LSHIFT; // y Y
this.aCharCodeMap[0x75] = 0x4200; this.aCharCodeMap[0x55] = 0x4200 + this.CHARCODE_LSHIFT; // u U
this.aCharCodeMap[0x69] = 0x4100; this.aCharCodeMap[0x49] = 0x4100 + this.CHARCODE_LSHIFT; // i I
this.aCharCodeMap[0x73] = 0x3700; this.aCharCodeMap[0x53] = 0x3700 + this.CHARCODE_LSHIFT; // s S
this.aCharCodeMap[0x64] = 0x3600; this.aCharCodeMap[0x44] = 0x3600 + this.CHARCODE_LSHIFT; // d D
this.aCharCodeMap[0x66] = 0x3500; this.aCharCodeMap[0x46] = 0x3500 + this.CHARCODE_LSHIFT; // f F
this.aCharCodeMap[0x67] = 0x3400; this.aCharCodeMap[0x47] = 0x3400 + this.CHARCODE_LSHIFT; // g G
this.aCharCodeMap[0x68] = 0x3300; this.aCharCodeMap[0x48] = 0x3300 + this.CHARCODE_LSHIFT; // h H
this.aCharCodeMap[0x6A] = 0x3200; this.aCharCodeMap[0x4A] = 0x3200 + this.CHARCODE_LSHIFT; // j J
this.aCharCodeMap[0x6B] = 0x3100; this.aCharCodeMap[0x4B] = 0x3100 + this.CHARCODE_LSHIFT; this.aCharCodeMap[0x5B] = 0x3100 + this.CHARCODE_LSHIFT; // k K [
this.aCharCodeMap[0x78] = 0x2700; this.aCharCodeMap[0x58] = 0x2700 + this.CHARCODE_LSHIFT; // x X
this.aCharCodeMap[0x63] = 0x2600; this.aCharCodeMap[0x43] = 0x2600 + this.CHARCODE_LSHIFT; // c C
this.aCharCodeMap[0x76] = 0x2500; this.aCharCodeMap[0x56] = 0x2500 + this.CHARCODE_LSHIFT; // v V
this.aCharCodeMap[0x62] = 0x2400; this.aCharCodeMap[0x42] = 0x2400 + this.CHARCODE_LSHIFT; // b B
this.aCharCodeMap[0x6E] = 0x2300; this.aCharCodeMap[0x4E] = 0x2300 + this.CHARCODE_LSHIFT; this.aCharCodeMap[0x5E] = 0x2300 + this.CHARCODE_LSHIFT; // n N ^
this.aCharCodeMap[0x6D] = 0x2200; this.aCharCodeMap[0x4D] = 0x2200 + this.CHARCODE_LSHIFT; this.aCharCodeMap[0x5D] = 0x2200 + this.CHARCODE_LSHIFT; // m M ]
this.aCharCodeMap[0x2C] = 0x2100; this.aCharCodeMap[0x3C] = 0x2100 + this.CHARCODE_LSHIFT; // , (0xBC) < (0xBC)
this.aCharCodeMap[0x71] = 0x1700; this.aCharCodeMap[0x51] = 0x1700 + this.CHARCODE_LSHIFT; // q Q
this.aCharCodeMap[0x61] = 0x1600; this.aCharCodeMap[0x41] = 0x1600 + this.CHARCODE_LSHIFT; // a A
this.aCharCodeMap[0x7A] = 0x1500; this.aCharCodeMap[0x5A] = 0x1500 + this.CHARCODE_LSHIFT; // z Z
this.aCharCodeMap[0x20] = 0x1400; // SPACE
this.aCharCodeMap[0x2F] = 0x1300; this.aCharCodeMap[0x3F] = 0x1300 + this.CHARCODE_LSHIFT; // / (0xBF) ? (0xBF)
this.aCharCodeMap[0x3B] = 0x1200; this.aCharCodeMap[0x2B] = 0x1200 + this.CHARCODE_LSHIFT; // ; (0xBA) + (0xBB)
this.aCharCodeMap[0x70] = 0x1100; this.aCharCodeMap[0x50] = 0x1100 + this.CHARCODE_LSHIFT; this.aCharCodeMap[0x40] = 0x1100 + this.CHARCODE_LSHIFT; // p P @
//this.aCharCodeMap[0x00] = 0x0700; // REPEAT (no mapping chosen for this key yet)
this.aCharCodeMap[this.CHARCODE_CTRL] = 0x0600; // CTRL
this.aCharCodeMap[this.CHARCODE_ESC] = 0x0500; // ESC
this.aCharCodeMap[this.CHARCODE_LSHIFT] = 0x0200; // LEFT-SHIFT
this.aCharCodeMap[this.CHARCODE_RSHIFT] = 0x0100; // RIGHT-SHIFT
this.aCharCodeMap[this.CHARCODE_SHIFTLOCK] = 0x0000; // SHIFT-LOCK
this.reset();
}
/**
* @this {C1PKeyboard}
*/
reset()
{
this.setModel(this.nDefaultModel);
/*
* The physical (not virtual) state of various shift keys,
* with the exception of SHIFT-LOCK, which needs to start in the
* "locked" position, regardless of the physical CAPS-LOCK state.
*
* QUESTION: In JavaScript, how do you query initial key states?
*/
this.bitsShift = this.BIT_SHIFTLOCK;
/*
* Every SET bit of bKbdRows represents an enabled row (this convention
* is the REVERSE of the C1P hardware, but I prefer it).
*/
this.bKbdRows = 0x00;
/*
* Every SET bit of abKbdCols represents an enabled column; again, this is
* the REVERSE of the C1P hardware, but I compensate for that difference with
* a quick XOR in updateMemory().
*
* Like bitsShift, this 8x8 array (8 byte values, each with 8 bits) represents
* the physical state of the keyboard, encoded in C1P format; the C1P won't
* actually see data this until updateMemory() decides it's time to propagate it.
*/
this.abKbdCols = [this.BIT_SHIFTLOCK,0x00,0x00,0x00,0x00,0x00,0x00,0x00];
/*
* After a new key event has updated abKbdCols, we "push" a copy of that
* updated keyboard state onto this array. updateMemory() will then "shift"
* the next copy off, update its own copy (abKbdColsLast), and then propagate
* that to the C1P's keyboard memory, once the CPU has had enough time to
* process the previous event (see nCyclesThreshold).
*/
this.aKbdStates = [];
/*
* When a key "down" is simulated on behalf of some charCode, I save
* the timer object responsible for simulating the key "up" here, so that
* if I detect the actual key going up sooner, I can cancel the timer and
* simulate the "up" immediately. Similarly, if another press for the same
* key arrives before last one expired (eg, auto-repeat), I need to cancel
* the previous timer for that key before setting another.
*
* NOTE: If this is anything other than an initial reset, then we need to
* make sure there are no outstanding timers before we blow the array away.
*/
if (this.aKeyTimers) {
for (var i in this.aKeyTimers) {
if (isNaN(+i)) continue; // ignore any non-numeric properties, if any
if (this.aKeyTimers[i]) clearTimeout(this.aKeyTimers[i]);
}
}
this.aKeyTimers = [];
this.prevCharDown = 0;
this.prevKeyDown = 0;
/*
* These save the last values written to keyboard memory, so that I can
* avoid rewriting the memory if the values haven't changed since the last update.
*/
this.bWriteLast = -1;
this.abKbdColsLast = this.abKbdCols;
/*
* Due to the way the C1P scans its keyboard rows (from R0 up to R7 *or* to the
* highest row for which a "down" key has just been detected), if we get back-to-back
* key events for, say, "I" and then "S", the C1P will see only the "S", never the "I",
* because "I" is on a higher row.
*
* That's why we have aKbdStates, which relies on the following CPU activity variables.
*/
this.nReadsSinceLastEvent = 0;
this.nWritesSinceLastEvent = 0;
this.nCyclesSinceLastEvent = 0;
/*
* Make sure the auto-injection buffer is empty, too (an injection could have been
* in progress on any reset after the first).
*/
this.sInjectBuffer = "";
}
/**
* @this {C1PKeyboard}
* @param {string} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "esc", "ctrl-c")
* @param {HTMLElement} control is the HTML control DOM object (eg, HTMLButtonElement)
* @param {string} [sValue] optional data value
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
setBinding(sHTMLType, sBinding, control, sValue)
{
/*
* I want to bind to the first caller (ie, the Screen), not subsequent ones (eg, the Panel)
*/
if (this.bindings[sBinding] === undefined) {
switch(sBinding) {
case "keyDown":
this.bindings[sBinding] = control;
control.onkeydown = function(kbd) {
return function(event) {
return kbd.keyEvent(event, true);
};
}(this);
return true;
case "keyPress":
this.bindings[sBinding] = control;
control.onkeypress = function(kbd) {
return function(event) {
return kbd.keyPress(event);
};
}(this);
return true;
case "keyUp":
this.bindings[sBinding] = control;
control.onkeyup = function(kbd) {
return function(event) {
return kbd.keyEvent(event, false);
};
}(this);
return true;
case "break":
/*
* The BREAK key is unusual: it requires us forcing the equivalent of someone pressing
* our "Reset" and "Run" buttons. As things stand, the Computer component is responsible
* for end-user "reset" requests, so we can simply arrange to call this.cmp.reset(true).
*
* NOTE: At the risk of making keyPressSimulate() a bit uglier, I also permit BREAK
* there, in case someone wants to "inject" the BREAK key; however, if it's followed by
* other injected keys, I'll need to avoid clearing the injection buffer on a reset;
* currently, reset() resets everything.
*/
this.bindings[sBinding] = control;
control.onclick = function(kbd) {
return function(event) {
if (DEBUG) kbd.println("keyPressSimulate(break)");
if (kbd.cmp) kbd.cmp.reset(true);
};
}(this);
return true;
default:
if (this.aButtonCodeMap[sBinding] !== undefined) {
this.bindings[sBinding] = control;
control.onclick = function(kbd, sButton, charCode) {
return function(event) {
if (DEBUG) kbd.println("keyPressSimulate(" + sButton + ")");
if (kbd.cpu) kbd.cpu.setFocus();
return !kbd.keyPressSimulate(charCode);
};
}(this, sBinding, this.aButtonCodeMap[sBinding]);
return true;
}
break;
}
}
return false;
}
/**
* @this {C1PKeyboard}
* @param {Array} abMemory
* @param {number} start
* @param {number} end
* @param {C1PCPU} cpu
*/
setBuffer(abMemory, start, end, cpu)
{
this.abMem = abMemory;
this.offKbd = start;
this.cbKbd = end - start + 1;
this.offKbdLimit = this.offKbd + this.cbKbd;
if (cpu) {
this.cpu = cpu;
if (DEBUG) cpu.addReadNotify(start, end, this, this.getByte);
cpu.addWriteNotify(start, end, this, this.setByte);
}
this.setReady();
}
/**
* @this {C1PKeyboard}
* @param {number} nModel
*/
setModel(nModel)
{
this.nModel = nModel;
/*
* Default to Model 600 behavior, where the keyboard status lines are inverted
* (ie, a zero bit indicates a key press).
*/
this.bInvert = 0xff;
if (this.nModel != 600) {
/*
* No inversion for model 542
*/
this.bInvert = 0x00;
this.println("updated keyboard model: " + this.nModel);
}
}
/**
* @this {C1PKeyboard}
* @param {boolean} fOn
* @param {C1PComputer} cmp
*
* We make a note of the Computer component, so that we can invoke its reset() method when our simulated
* BREAK key is pressed, and we query the Debugger component so that we can use its info() and halt() functions,
* which we use to buffer information without adversely affecting timing and then dump later using the Debugger's
* "info" command.
*/
setPower(fOn, cmp)
{
if (fOn && !this.flags.powered) {
this.flags.powered = true;
this.cmp = cmp;
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
}
}
/**
* @this {C1PKeyboard}
* @param {boolean} [fReady] is assumed to indicate "ready" unless EXPLICITLY set to false
*/
setReady(fReady)
{
this.iOS = Web.isUserAgent("iOS");
this.fMobile = (this.iOS || Web.isUserAgent("Android"));
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(this.dbg.MESSAGE_KBD)) {
this.dbg.message("mobile keyboard support: " + (this.fMobile? "true" : "false") + " (" + window.navigator.userAgent + ")");
}
super.setReady();
}
/**
* calcReleaseDelay(fRepeat)
*
* This attempts to scale our default "release" delay appropriately for the current CPU speed.
*
* Note that if the effective CPU speed exceeds 16Mhz, it becomes very difficult to rely on timer-driven key events
* (even the shortest available timer delay still gives the CPU too much time, so it thinks that even the briefest key
* press represents a held key, resulting in multiple keystrokes). We deal with this by artificially limiting the top
* speed in the CPU component (the current limit for "fast" mode is 8Mhz; see CPU.mhzFast)
*
* @this {C1PKeyboard}
* @param {boolean} fRepeat is true if a timeout had already been active for the current key
* @return {number}
*/
calcReleaseDelay(fRepeat)
{
/*
* NOTE: This delay affects only the "up" delay, not repeat delay, but it's useful to have an initial
* "up" delay that's sufficiently large to ensure the native machine's auto-repeat behavior cooperates
* with the virtual machine's auto-repeat behavior. msReleaseDelay is the initial delay, msReleaseRepeat
* is the subsequent delay.
*
* Unfortunately, with a large initial delay, we need to enable the auto-clear code in the keyEvent()
* handler, otherwise doing things like pressing ENTER repeatedly will result in sluggish behavior
* (because you can generally press/release/repress keys faster than they will auto-repeat).
*/
var msDelay = (fRepeat? this.msReleaseRepeat: this.msReleaseDelay);
if (this.cpu && this.cpu.mhz) {
msDelay /= this.cpu.mhz;
}
return msDelay;
}
/**
* @this {C1PKeyboard}
* @param {number} [notCharCode]
*/
autoClear(notCharCode)
{
if (this.prevCharDown && (notCharCode === undefined || notCharCode != this.prevCharDown)) {
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(this.dbg.MESSAGE_KBD)) {
this.dbg.message("autoClear(" + Str.toHexByte(this.prevCharDown) + ")");
}
Component.assert(this.aKeyTimers[this.prevCharDown]);
clearTimeout(this.aKeyTimers[this.prevCharDown]);
this.keyEventSimulate(this.prevCharDown, false, this.SIMCODE_AUTOCLEAR);
}
}
/**
* @this {C1PKeyboard}
* @param {string} sKeyCodes
* @param {number} [msDelay] is an optional injection delay (default is msInjectDelay)
*/
injectKeys(sKeyCodes, msDelay)
{
this.sInjectBuffer = sKeyCodes;
if (DEBUG) this.log("injectKeys(" + this.sInjectBuffer.split("\n").join("\\n") + ")");
this.injectKeysFromBuffer(msDelay || this.msInjectDelay);
}
/**
* @this {C1PKeyboard}
* @param {number} msDelay is the delay between injected keys
*/
injectKeysFromBuffer(msDelay)
{
if (this.sInjectBuffer.length > 0) {
var ch = this.sInjectBuffer.charCodeAt(0);
/*
* I could require all callers to supply CRs instead of LFs, but this is friendlier.
*/
if (ch == 0x0a)
ch = 0x0d;
/*
* Also, if upper-case characters are being injected, convert them to lower-case, and rely
* on the virtual SHIFT-LOCK remaining locked for the duration; otherwise, we'd have to simulate
* SHIFT key presses around every character (or around the entire set of characters) as well.
*
* UPDATE: Even though keyPressSimulate() currently has some code to do this automatically now,
* it's really intended as a work-around for a SHIFT-related problem on iOS devices only, so
* we can't rely on that in the general case.
*/
if (ch >= 0x41 && ch <= 0x5A)
ch += 0x20;
this.sInjectBuffer = this.sInjectBuffer.substr(1);
this.keyPressSimulate(ch);
}
if (this.sInjectBuffer.length > 0) {
setTimeout(function(kbd) { return function() {kbd.injectKeysFromBuffer(msDelay);}; }(this), msDelay);
}
}
/**
* @this {C1PKeyboard}
* @param {Object} event
* @param {boolean} fDown is true if called for a keyDown event, false if called for a keyUp event
* @return {boolean} true to pass the event along, false to consume it
*/
keyEvent(event, fDown)
{
var fPass;
var fAutoClear = !fDown;
var keyCode = event.keyCode;
if (fDown) this.prevKeyDown = keyCode;
if (keyCode == this.CHARCODE_LSHIFT - this.PSEUDO_CHARCODE) {
this.bitsShift &= ~this.BIT_LSHIFT;
if (fDown) this.bitsShift |= this.BIT_LSHIFT;
keyCode += this.PSEUDO_CHARCODE;
fAutoClear = false;
}
else
if (keyCode == this.CHARCODE_RSHIFT - this.PSEUDO_CHARCODE) {
this.bitsShift &= ~this.BIT_RSHIFT;
if (fDown) this.bitsShift |= this.BIT_RSHIFT;
keyCode += this.PSEUDO_CHARCODE;
fAutoClear = false;
}
else
if (keyCode == this.CHARCODE_CTRL - this.PSEUDO_CHARCODE) {
this.bitsShift &= ~this.BIT_CTRL;
if (fDown) this.bitsShift |= this.BIT_CTRL;
keyCode += this.PSEUDO_CHARCODE;
fAutoClear = false;
}
else
if (keyCode == this.CHARCODE_SHIFTLOCK - this.PSEUDO_CHARCODE) {
/*
* FYI, this generates a "down" event ONLY when getting locked, and an "up" event ONLY
* when getting unlocked--which is exactly what I want, even though that may seem a little
* counter-intuitive (since the key itself actually went down AND up for each event).
*
* Moreover, since most people do NOT have CAPS-LOCK enabled, whereas the C1P needs it
* enabled by default, we invert fDown, so that if the user enables CAPS-LOCK for some
* reason, we treat is as *disabling* SHIFT-LOCK, and vice versa.
*/
fDown = !fDown;
this.bitsShift &= ~this.BIT_SHIFTLOCK;
if (fDown) this.bitsShift |= this.BIT_SHIFTLOCK;
keyCode += this.PSEUDO_CHARCODE;
fAutoClear = false;
}
else
if (keyCode == this.KEYCODE_COMMAND) {
/*
* Avoid interfering with useful Browser key commands, like COMMAND-Q, COMMAND-T, etc.
*/
this.bitsShift &= ~this.BIT_COMMAND;
if (fDown) this.bitsShift |= this.BIT_COMMAND;
fAutoClear = false;
fPass = true;
}
else
if (keyCode == this.KEYCODE_TAB) {
/*
* If I don't consume TAB on the "down" event, then that's all I'll see, because the
* browser will see it and give focus to the next control. But the "down" side is that
* that no "press" event will be generated. This puts it in the same category as ESC,
* which also generates "down" and "up" events (LOTS of "down" events for that matter),
* but no "press" event. However, the C1P has no TAB key, so it's safe to completely ignore.
*/
fPass = fAutoClear = false;
}
else
if (keyCode == this.KEYCODE_ESC || keyCode == this.KEYCODE_DELETE) {
/*
* I don't get keyPress events for ESC (why?) and I never want the browser to act on DELETE
* (which does double-duty as the "Back" button and leaves the current page), so I have to
* simulate them now.
*
* Note that I call the "press" simulate method and NOT the "event" simulate method, because
* the former takes care of simulating both individual "down" and "up" events.
*/
if (DEBUG && DEBUGGER && keyCode == this.KEYCODE_ESC && this.dbg) this.dbg.halt();
fPass = (fDown? !this.keyPressSimulate(keyCode) : false);
}
else {
/*
* Pass on everything else; I'll take care of this key at the keyPress stage, not the
* the keyDown or keyUp stage.
*/
fPass = true;
/*
* At this point, I have a difficult choice to make: leave fAutoClear true for any remaining
* "up" events, so that keys will repeat immediately when released/pressed repeatedly (most
* noticeable with the Enter key), or set fAutoClear to false to ensure that polling apps have
* enough time to see every key press.
*
* I've decided that the former is more important than the latter, so if polling apps are still
* missing keystrokes, then perhaps nCyclesThreshold needs to be supplemented in some way.
*
* fAutoClear = false;
*/
}
if (fAutoClear) {
/*
* When you use a command like COMMAND-T, I see the COMMAND key going down, but not going up,
* so I think the COMMAND key is still down and ignore all input; to easily get out of that state,
* I clear our internal BIT_COMMAND whenever I see ANY key go up (well, ALMOST any key; cases
* above that explicitly clear fAutoClear -- such as the COMMAND key itself -- are exceptions
* to the rule).
*/
this.bitsShift &= ~this.BIT_COMMAND;
/*
* I don't reliably get keyDown/keyUp events for all keys on all devices, but for those devices that
* I DO, it seems like a good idea to cancel any pending key "up" simulation on receipt of the actual
* keyUp event.
*
* However, the following code is problematic for Safari on iOS devices, which as noted above, doesn't
* generate keyDown/keyUp events until after the press operation is complete, and then they are generated
* in rapid succession, which doesn't give the C1P enough time to detect the key. So I simply don't do
* this on iOS devices.
*/
if (!this.fMobile && keyCode == this.prevKeyDown) this.autoClear();
}
if (fPass === undefined) {
fPass = !this.keyEventSimulate(keyCode, fDown, this.SIMCODE_KEYEVENT);
}
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(this.dbg.MESSAGE_KBD)) {
this.dbg.message(/*(fDown?"\n":"") +*/ "key" + (fDown?"Down":"Up") + "(" + Str.toHexByte(keyCode) + "): " + (fPass? "pass" : "consume"));
}
return fPass;
}
/**
* @this {C1PKeyboard}
* @param {Object} event
* @return {boolean} true to pass the event along, false to consume it
*
* We've stopped relying on keyPress for keyboard emulation purposes, but it's still handy to hook and monitor
* when debugging.
*/
keyPress(event)
{
var fPass = true;
/*
* Browser-independent charCode extraction...
*/
event = event || window.event;
var charCode = event.which || event.keyCode;
/*
* Let's stop any injection currently in progress, too
*/
this.sInjectBuffer = "";
if (this.bitsShift & this.BIT_COMMAND)
this.bitsShift &= ~this.BIT_COMMAND;
else
fPass = !this.keyPressSimulate(charCode);
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(this.dbg.MESSAGE_KBD)) {
this.dbg.message("keyPress(" + Str.toHexByte(charCode) + "): " + (fPass? "pass" : "consume"));
}
return fPass;
}
/**
* @this {C1PKeyboard}
* @param {number} charCode
* @return {boolean} true if successfully simulated, false if unrecognized/unsupported key
*/
keyPressSimulate(charCode)
{
var fSimulated = false;
if (charCode == this.CHARCODE_BREAK) {
/*
* The BREAK key is not wired up to the keyboard like the other keys are, but we simulate
* it here, so that it can be injected like any other key.
*/
if (this.cmp) {
this.cmp.reset(true);
fSimulated = true;
}
}
else {
/*
* WARNING: The next line is why you cannot use SHIFT-N, SHIFT-O, SHIFT-P, etc. But without it,
* iOS devices with the annoying "autocapitalization" feature enabled make the keyboard unusable
* by default. The trade-off is: either require all iOS users to first tap the shift key to turn
* "autocapitalization" off, or lose the ability to type any of the special shifted alphabetic keys.
* I choose the latter, because I have friendlier aliases already defined for those keys (eg,
* ^, DELETE, and @).
*
* Furthermore, by doing this for iOS (and Android) devices ONLY, other platforms retain the ability
* to use those special key combos.
*/
if (this.fMobile) {
if (charCode >= 0x41 && charCode <= 0x5A)
charCode += 0x20;
}
/*
* Auto-clear any previous down key EXCEPT for charCode (because it may be held and repeating).
*/
this.autoClear(charCode);
if (this.keyEventSimulate(charCode, true, this.SIMCODE_KEYPRESS)) {
/*
* If CPU speed is unlimited, then we switch to an alternate approach, which is to immediately
* queue a "release" event as well. The problem with the original timer-based approach at high
* speeds is that the the CPU may get lucky and execute a LOT of instructions between delivery
* of the keyPress event and the "keyTimeout" event. In that case, even enabling keyboard polling
* detection in updateMemory() won't entirely help -- although we do that, too -- because JavaScript
* events are delivered synchronously, so it may simply take too long for the "keyTimeout" event
* to arrive.
*
* Why don't we ALWAYS do this? Because in the normal case (SPEED_SLOW, and even SPEED_FAST) we want
* to faithfully simulate how long a key is held, so that features like auto-repeat work properly.
* You'll notice in the SPEED_MAX case, holding a key no longer has any effect; even though multiple
* keyPress events WILL arrive, if we simulate a release immediately after each one, then repeat
* is defeated. Also, the keyboard polling detection code in updateMemory() doesn't work well for
* all apps.
*/
if (this.cpu.speed == this.cpu.SPEED_MAX) {
this.keyEventSimulate(charCode, false, this.SIMCODE_KEYRELEASE);
}
else {
var fRepeat = false;
if (this.aKeyTimers[charCode]) {
clearTimeout(this.aKeyTimers[charCode]);
fRepeat = true;
}
var msDelay = this.calcReleaseDelay(fRepeat);
this.aKeyTimers[this.prevCharDown = charCode] = setTimeout(function(kbd) { return function() {kbd.keyEventSimulate(charCode, false, kbd.SIMCODE_KEYTIMEOUT);}; }(this), msDelay);
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(this.dbg.MESSAGE_KBD)) {
this.dbg.message("keyPressSimulate(" + Str.toHexByte(charCode) + "): setTimeout()");
}
}
fSimulated = true;
}
}
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(this.dbg.MESSAGE_KBD)) {
this.dbg.message("keyPressSimulate(" + Str.toHexByte(charCode) + "): " + (fSimulated? "true" : "false"));
}
return fSimulated;
}
/**
* @this {C1PKeyboard}
* @param {number} charCode
* @param {boolean} fDown
* @param {number} simCode indicates the origin of the event
* @return {boolean} true if successfully simulated, false if unrecognized/unsupported key
*/
keyEventSimulate(charCode, fDown, simCode)
{
var fSimulated = false;
if (!fDown) {
this.aKeyTimers[charCode] = null;
if (this.prevCharDown == charCode) this.prevCharDown = 0;
}
var bShift = 0;
var bCode = this.aCharCodeMap[charCode];
if (bCode === undefined) {
/*
* Perhaps we're dealing with a CTRL variation of an alphabetic key; this won't
* affect non-CTRL-key combos like CR or LF, because they're defined in aCharCodeMap,
* and this bit of code relieves us from having to explicitly define every CTRL-letter
* possibility in aCharCodeMap. However, CTRL-anything-else is a different matter.
*/
if (charCode >= 0x01 && charCode <= 0x1A) {
charCode += 0x40;
bShift = this.CHARCODE_CTRL;
}
bCode = this.aCharCodeMap[charCode];
}
if (bCode !== undefined) {
var iRow = bCode >> 12;
var iCol = (bCode >> 8) & 0xf;
if (!bShift) bShift = bCode & 0xff;
if (fDown) {
this.abKbdCols[iRow] |= 1 << iCol;
if (bShift == this.CHARCODE_CTRL)
this.abKbdCols[0] |= this.BIT_CTRL;
else
if (bShift == this.CHARCODE_LSHIFT)
this.abKbdCols[0] |= this.BIT_LSHIFT;
else
if (bShift == this.CHARCODE_RSHIFT)
this.abKbdCols[0] |= this.BIT_RSHIFT;
else
this.abKbdCols[0] &= ~this.BITS_SIMULATE;
}
else {
this.abKbdCols[iRow] &= ~(1 << iCol);
this.abKbdCols[0] &= ~this.BITS_SIMULATE;
this.abKbdCols[0] |= (this.bitsShift & this.BITS_SIMULATE);
}
var fPropagate = (simCode == this.SIMCODE_KEYPRESS && !this.aKbdStates.length);
this.aKbdStates.push(this.abKbdCols.slice());
this.updateMemory(fPropagate);
fSimulated = true;
}
if (DEBUG && this.dbg) this.dbg.info("keyEventSimulate(" + Str.toHexByte(charCode) + "," + (fDown?"down":"up") + "," + this.aSimCodeDescs[simCode] + "): " + (fSimulated? "true" : "false"));
return fSimulated;
}
/**
* @this {C1PKeyboard}
* @param {number} addr
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to read the specified addr)
*
* NOTE: As long as we rely on the CPU processing a certain number of cycles (nCyclesThreshold) before
* propagating the next kbd state, and not how many reads and/or writes the CPU has performed, we could
* eliminate the overhead of this read-notification handler.
*
* It's useful for diagnostic purposes, which is why it's still here.
*/
getByte(addr, addrFrom)
{
/*
* Don't trigger any further hardware emulation (beyond what we've already stored in memory) if
* the Debugger performed this read (need a special Debugger I/O command if/when you really want to do that).
*/
if (DEBUG) {
if (addrFrom !== undefined) {
this.nReadsSinceLastEvent++;
if (DEBUG && this.dbg) this.dbg.info("reading kbd " + Str.toHexWord(addr) + " @" + this.cpu.getCycles() + " cycles");
}
}
}
/**
* @this {C1PKeyboard}
* @param {number} addr
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to write the specified addr)
*
* NOTE: Ordinarily, I wouldn't allow Debugger writes (addrFrom === undefined) to interfere with the simulated
* hardware state, but for now, I find it useful to be able to prod the simulation code directly from the Debugger.
*/
setByte(addr, addrFrom)
{
var b = this.cpu.getByte(addr);
this.bKbdRows = b ^ this.bInvert;
this.nWritesSinceLastEvent++;
this.updateMemory(false, addr, b);
}
/**
* @this {C1PKeyboard}
* @param {boolean} fPropagate is true to propagate immediately, false to use normal propagation
* @param {number} [addr] is the memory address to update; default is the entire memory range
* @param {number} [bWrite] is the value of any immediately preceding write, or undefined if none
*
* Update emulated keyboard memory. By updating the keyboard memory whenever it's written to,
* as well as whenever a key is pressed or released, I avoid the hit of a read-notification handler.
* Besides, read-notification handlers are called only AFTER the read has been performed, so it