jsbeeb 1.22.4 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/6502.opcodes.js +191 -110
- package/src/jsbeeb.css +33 -0
- package/src/main.js +5 -6
- package/src/models.js +23 -1
- package/src/web/config.js +3 -0
- package/src/web/debug.js +2 -44
- package/src/web/icons.js +42 -0
- package/src/web/keyboard-setup.js +11 -2
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"name": "jsbeeb",
|
|
8
8
|
"description": "Emulate a BBC Micro",
|
|
9
9
|
"repository": "git@github.com:mattgodbolt/jsbeeb.git",
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.23.0",
|
|
11
11
|
"//engines": "If you change the version of Node, it must also be updated at the top of the Dockerfile.",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=24.15.0"
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"@popperjs/core": "^2.11.8",
|
|
30
30
|
"argparse": "^3.0.0",
|
|
31
31
|
"bootstrap": "^5.3.8",
|
|
32
|
+
"bootstrap-icons": "^1.13.1",
|
|
32
33
|
"bootswatch": "^5.3.8",
|
|
33
34
|
"pako": "^3.0.1",
|
|
34
35
|
"sharp": "^0.35.3",
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
"vitest": "^4.0.10"
|
|
53
54
|
},
|
|
54
55
|
"optionalDependencies": {
|
|
55
|
-
"electron": "^
|
|
56
|
+
"electron": "^44.0.0",
|
|
56
57
|
"electron-builder": "^26.15.3",
|
|
57
58
|
"electron-store": "^11.0.2"
|
|
58
59
|
},
|
package/src/6502.opcodes.js
CHANGED
|
@@ -525,254 +525,269 @@ function getOp(op, arg) {
|
|
|
525
525
|
throw new Error(`Unrecognised operation '${op}'`);
|
|
526
526
|
}
|
|
527
527
|
|
|
528
|
-
|
|
528
|
+
// Undocumented opcodes carry a leading "*", as 6502 listings conventionally show them.
|
|
529
|
+
function splitUndocumented(markedOpcodes) {
|
|
530
|
+
const opcodes = {};
|
|
531
|
+
const undocumented = new Set();
|
|
532
|
+
for (const [op, text] of Object.entries(markedOpcodes)) {
|
|
533
|
+
if (text.startsWith("*")) {
|
|
534
|
+
undocumented.add(Number(op));
|
|
535
|
+
opcodes[op] = text.slice(1);
|
|
536
|
+
} else {
|
|
537
|
+
opcodes[op] = text;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return { opcodes, undocumented };
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const { opcodes: opcodes6502, undocumented: undocumented6502 } = splitUndocumented({
|
|
529
544
|
0x00: "BRK",
|
|
530
545
|
0x01: "ORA (,x)",
|
|
531
|
-
0x03: "SLO (,x)",
|
|
532
|
-
0x04: "NOP zp",
|
|
546
|
+
0x03: "*SLO (,x)",
|
|
547
|
+
0x04: "*NOP zp",
|
|
533
548
|
0x05: "ORA zp",
|
|
534
549
|
0x06: "ASL zp",
|
|
535
|
-
0x07: "SLO zp",
|
|
550
|
+
0x07: "*SLO zp",
|
|
536
551
|
0x08: "PHP",
|
|
537
552
|
0x09: "ORA imm",
|
|
538
553
|
0x0a: "ASL A",
|
|
539
|
-
0x0b: "ANC imm",
|
|
540
|
-
0x0c: "NOP abs",
|
|
554
|
+
0x0b: "*ANC imm",
|
|
555
|
+
0x0c: "*NOP abs",
|
|
541
556
|
0x0d: "ORA abs",
|
|
542
557
|
0x0e: "ASL abs",
|
|
543
|
-
0x0f: "SLO abs",
|
|
558
|
+
0x0f: "*SLO abs",
|
|
544
559
|
0x10: "BPL branch",
|
|
545
560
|
0x11: "ORA (),y",
|
|
546
|
-
0x13: "SLO (),y",
|
|
547
|
-
0x14: "NOP zp,x",
|
|
561
|
+
0x13: "*SLO (),y",
|
|
562
|
+
0x14: "*NOP zp,x",
|
|
548
563
|
0x15: "ORA zp,x",
|
|
549
564
|
0x16: "ASL zp,x",
|
|
550
|
-
0x17: "SLO zp,x",
|
|
565
|
+
0x17: "*SLO zp,x",
|
|
551
566
|
0x18: "CLC",
|
|
552
567
|
0x19: "ORA abs,y",
|
|
553
|
-
0x1a: "NOP",
|
|
554
|
-
0x1b: "SLO abs,y",
|
|
555
|
-
0x1c: "NOP abs,x",
|
|
568
|
+
0x1a: "*NOP",
|
|
569
|
+
0x1b: "*SLO abs,y",
|
|
570
|
+
0x1c: "*NOP abs,x",
|
|
556
571
|
0x1d: "ORA abs,x",
|
|
557
572
|
0x1e: "ASL abs,x",
|
|
558
|
-
0x1f: "SLO abs,x",
|
|
573
|
+
0x1f: "*SLO abs,x",
|
|
559
574
|
0x20: "JSR abs",
|
|
560
575
|
0x21: "AND (,x)",
|
|
561
|
-
0x23: "RLA (,x)",
|
|
576
|
+
0x23: "*RLA (,x)",
|
|
562
577
|
0x24: "BIT zp",
|
|
563
578
|
0x25: "AND zp",
|
|
564
579
|
0x26: "ROL zp",
|
|
565
|
-
0x27: "RLA zp",
|
|
580
|
+
0x27: "*RLA zp",
|
|
566
581
|
0x28: "PLP",
|
|
567
582
|
0x29: "AND imm",
|
|
568
583
|
0x2a: "ROL A",
|
|
569
|
-
0x2b: "ANC imm",
|
|
584
|
+
0x2b: "*ANC imm",
|
|
570
585
|
0x2c: "BIT abs",
|
|
571
586
|
0x2d: "AND abs",
|
|
572
587
|
0x2e: "ROL abs",
|
|
573
|
-
0x2f: "RLA abs",
|
|
588
|
+
0x2f: "*RLA abs",
|
|
574
589
|
0x30: "BMI branch",
|
|
575
590
|
0x31: "AND (),y",
|
|
576
|
-
0x33: "RLA (),y",
|
|
577
|
-
0x34: "NOP zp,x",
|
|
591
|
+
0x33: "*RLA (),y",
|
|
592
|
+
0x34: "*NOP zp,x",
|
|
578
593
|
0x35: "AND zp,x",
|
|
579
594
|
0x36: "ROL zp,x",
|
|
580
|
-
0x37: "RLA zp,x",
|
|
595
|
+
0x37: "*RLA zp,x",
|
|
581
596
|
0x38: "SEC",
|
|
582
597
|
0x39: "AND abs,y",
|
|
583
|
-
0x3a: "NOP",
|
|
584
|
-
0x3b: "RLA abs,y",
|
|
585
|
-
0x3c: "NOP abs,x",
|
|
598
|
+
0x3a: "*NOP",
|
|
599
|
+
0x3b: "*RLA abs,y",
|
|
600
|
+
0x3c: "*NOP abs,x",
|
|
586
601
|
0x3d: "AND abs,x",
|
|
587
602
|
0x3e: "ROL abs,x",
|
|
588
|
-
0x3f: "RLA abs,x",
|
|
603
|
+
0x3f: "*RLA abs,x",
|
|
589
604
|
0x40: "RTI",
|
|
590
605
|
0x41: "EOR (,x)",
|
|
591
|
-
0x43: "SRE (,x)",
|
|
592
|
-
0x44: "NOP zp",
|
|
606
|
+
0x43: "*SRE (,x)",
|
|
607
|
+
0x44: "*NOP zp",
|
|
593
608
|
0x45: "EOR zp",
|
|
594
609
|
0x46: "LSR zp",
|
|
595
|
-
0x47: "SRE zp",
|
|
610
|
+
0x47: "*SRE zp",
|
|
596
611
|
0x48: "PHA",
|
|
597
612
|
0x49: "EOR imm",
|
|
598
613
|
0x4a: "LSR A",
|
|
599
|
-
0x4b: "ASR imm",
|
|
614
|
+
0x4b: "*ASR imm",
|
|
600
615
|
0x4c: "JMP abs",
|
|
601
616
|
0x4d: "EOR abs",
|
|
602
617
|
0x4e: "LSR abs",
|
|
603
|
-
0x4f: "SRE abs",
|
|
618
|
+
0x4f: "*SRE abs",
|
|
604
619
|
0x50: "BVC branch",
|
|
605
620
|
0x51: "EOR (),y",
|
|
606
|
-
0x53: "SRE (),y",
|
|
607
|
-
0x54: "NOP zp,x",
|
|
621
|
+
0x53: "*SRE (),y",
|
|
622
|
+
0x54: "*NOP zp,x",
|
|
608
623
|
0x55: "EOR zp,x",
|
|
609
624
|
0x56: "LSR zp,x",
|
|
610
|
-
0x57: "SRE zp,x",
|
|
625
|
+
0x57: "*SRE zp,x",
|
|
611
626
|
0x58: "CLI",
|
|
612
627
|
0x59: "EOR abs,y",
|
|
613
|
-
0x5a: "NOP",
|
|
614
|
-
0x5b: "SRE abs,y",
|
|
615
|
-
0x5c: "NOP abs,x",
|
|
628
|
+
0x5a: "*NOP",
|
|
629
|
+
0x5b: "*SRE abs,y",
|
|
630
|
+
0x5c: "*NOP abs,x",
|
|
616
631
|
0x5d: "EOR abs,x",
|
|
617
632
|
0x5e: "LSR abs,x",
|
|
618
|
-
0x5f: "SRE abs,x",
|
|
633
|
+
0x5f: "*SRE abs,x",
|
|
619
634
|
0x60: "RTS",
|
|
620
635
|
0x61: "ADC (,x)",
|
|
621
|
-
0x63: "RRA (,x)",
|
|
622
|
-
0x64: "NOP zp",
|
|
636
|
+
0x63: "*RRA (,x)",
|
|
637
|
+
0x64: "*NOP zp",
|
|
623
638
|
0x65: "ADC zp",
|
|
624
639
|
0x66: "ROR zp",
|
|
625
|
-
0x67: "RRA zp",
|
|
640
|
+
0x67: "*RRA zp",
|
|
626
641
|
0x68: "PLA",
|
|
627
642
|
0x69: "ADC imm",
|
|
628
643
|
0x6a: "ROR A",
|
|
629
|
-
0x6b: "ARR imm",
|
|
644
|
+
0x6b: "*ARR imm",
|
|
630
645
|
0x6c: "JMP (abs)",
|
|
631
646
|
0x6d: "ADC abs",
|
|
632
647
|
0x6e: "ROR abs",
|
|
633
|
-
0x6f: "RRA abs",
|
|
648
|
+
0x6f: "*RRA abs",
|
|
634
649
|
0x70: "BVS branch",
|
|
635
650
|
0x71: "ADC (),y",
|
|
636
|
-
0x73: "RRA (),y",
|
|
637
|
-
0x74: "NOP zp,x",
|
|
651
|
+
0x73: "*RRA (),y",
|
|
652
|
+
0x74: "*NOP zp,x",
|
|
638
653
|
0x75: "ADC zp,x",
|
|
639
654
|
0x76: "ROR zp,x",
|
|
640
|
-
0x77: "RRA zp,x",
|
|
655
|
+
0x77: "*RRA zp,x",
|
|
641
656
|
0x78: "SEI",
|
|
642
657
|
0x79: "ADC abs,y",
|
|
643
|
-
0x7a: "NOP",
|
|
644
|
-
0x7b: "RRA abs,y",
|
|
645
|
-
0x7c: "NOP abs,x",
|
|
658
|
+
0x7a: "*NOP",
|
|
659
|
+
0x7b: "*RRA abs,y",
|
|
660
|
+
0x7c: "*NOP abs,x",
|
|
646
661
|
0x7d: "ADC abs,x",
|
|
647
662
|
0x7e: "ROR abs,x",
|
|
648
|
-
0x7f: "RRA abs,x",
|
|
649
|
-
0x80: "NOP imm",
|
|
663
|
+
0x7f: "*RRA abs,x",
|
|
664
|
+
0x80: "*NOP imm",
|
|
650
665
|
0x81: "STA (,x)",
|
|
651
|
-
0x82: "NOP imm",
|
|
652
|
-
0x83: "SAX (,x)",
|
|
666
|
+
0x82: "*NOP imm",
|
|
667
|
+
0x83: "*SAX (,x)",
|
|
653
668
|
0x84: "STY zp",
|
|
654
669
|
0x85: "STA zp",
|
|
655
670
|
0x86: "STX zp",
|
|
656
|
-
0x87: "SAX zp",
|
|
671
|
+
0x87: "*SAX zp",
|
|
657
672
|
0x88: "DEY",
|
|
658
|
-
0x89: "NOP imm",
|
|
673
|
+
0x89: "*NOP imm",
|
|
659
674
|
0x8a: "TXA",
|
|
660
|
-
0x8b: "ANE imm",
|
|
675
|
+
0x8b: "*ANE imm",
|
|
661
676
|
0x8c: "STY abs",
|
|
662
677
|
0x8d: "STA abs",
|
|
663
678
|
0x8e: "STX abs",
|
|
664
|
-
0x8f: "SAX abs",
|
|
679
|
+
0x8f: "*SAX abs",
|
|
665
680
|
0x90: "BCC branch",
|
|
666
681
|
0x91: "STA (),y",
|
|
667
|
-
0x93: "SHA (),y",
|
|
682
|
+
0x93: "*SHA (),y",
|
|
668
683
|
0x94: "STY zp,x",
|
|
669
684
|
0x95: "STA zp,x",
|
|
670
685
|
0x96: "STX zp,y",
|
|
671
|
-
0x97: "SAX zp,y",
|
|
686
|
+
0x97: "*SAX zp,y",
|
|
672
687
|
0x98: "TYA",
|
|
673
688
|
0x99: "STA abs,y",
|
|
674
689
|
0x9a: "TXS",
|
|
675
|
-
0x9b: "SHS abs,y",
|
|
676
|
-
0x9c: "SHY abs,x",
|
|
690
|
+
0x9b: "*SHS abs,y",
|
|
691
|
+
0x9c: "*SHY abs,x",
|
|
677
692
|
0x9d: "STA abs,x",
|
|
678
|
-
0x9e: "SHX abs,y",
|
|
679
|
-
0x9f: "SHA abs,y",
|
|
693
|
+
0x9e: "*SHX abs,y",
|
|
694
|
+
0x9f: "*SHA abs,y",
|
|
680
695
|
0xa0: "LDY imm",
|
|
681
696
|
0xa1: "LDA (,x)",
|
|
682
697
|
0xa2: "LDX imm",
|
|
683
|
-
0xa3: "LAX (,x)",
|
|
698
|
+
0xa3: "*LAX (,x)",
|
|
684
699
|
0xa4: "LDY zp",
|
|
685
700
|
0xa5: "LDA zp",
|
|
686
701
|
0xa6: "LDX zp",
|
|
687
|
-
0xa7: "LAX zp",
|
|
702
|
+
0xa7: "*LAX zp",
|
|
688
703
|
0xa8: "TAY",
|
|
689
704
|
0xa9: "LDA imm",
|
|
690
705
|
0xaa: "TAX",
|
|
691
|
-
0xab: "LXA imm",
|
|
706
|
+
0xab: "*LXA imm",
|
|
692
707
|
0xac: "LDY abs",
|
|
693
708
|
0xad: "LDA abs",
|
|
694
709
|
0xae: "LDX abs",
|
|
695
|
-
0xaf: "LAX abs",
|
|
710
|
+
0xaf: "*LAX abs",
|
|
696
711
|
0xb0: "BCS branch",
|
|
697
712
|
0xb1: "LDA (),y",
|
|
698
|
-
0xb3: "LAX (),y",
|
|
713
|
+
0xb3: "*LAX (),y",
|
|
699
714
|
0xb4: "LDY zp,x",
|
|
700
715
|
0xb5: "LDA zp,x",
|
|
701
716
|
0xb6: "LDX zp,y",
|
|
702
|
-
0xb7: "LAX zp,y",
|
|
717
|
+
0xb7: "*LAX zp,y",
|
|
703
718
|
0xb8: "CLV",
|
|
704
719
|
0xb9: "LDA abs,y",
|
|
705
720
|
0xba: "TSX",
|
|
706
|
-
0xbb: "LAS abs,y",
|
|
721
|
+
0xbb: "*LAS abs,y",
|
|
707
722
|
0xbc: "LDY abs,x",
|
|
708
723
|
0xbd: "LDA abs,x",
|
|
709
724
|
0xbe: "LDX abs,y",
|
|
710
|
-
0xbf: "LAX abs,y",
|
|
725
|
+
0xbf: "*LAX abs,y",
|
|
711
726
|
0xc0: "CPY imm",
|
|
712
727
|
0xc1: "CMP (,x)",
|
|
713
|
-
0xc2: "NOP imm",
|
|
714
|
-
0xc3: "DCP (,x)",
|
|
728
|
+
0xc2: "*NOP imm",
|
|
729
|
+
0xc3: "*DCP (,x)",
|
|
715
730
|
0xc4: "CPY zp",
|
|
716
731
|
0xc5: "CMP zp",
|
|
717
732
|
0xc6: "DEC zp",
|
|
718
|
-
0xc7: "DCP zp",
|
|
733
|
+
0xc7: "*DCP zp",
|
|
719
734
|
0xc8: "INY",
|
|
720
735
|
0xc9: "CMP imm",
|
|
721
736
|
0xca: "DEX",
|
|
722
|
-
0xcb: "SBX imm",
|
|
737
|
+
0xcb: "*SBX imm",
|
|
723
738
|
0xcc: "CPY abs",
|
|
724
739
|
0xcd: "CMP abs",
|
|
725
740
|
0xce: "DEC abs",
|
|
726
|
-
0xcf: "DCP abs",
|
|
741
|
+
0xcf: "*DCP abs",
|
|
727
742
|
0xd0: "BNE branch",
|
|
728
743
|
0xd1: "CMP (),y",
|
|
729
|
-
0xd3: "DCP (),y",
|
|
730
|
-
0xd4: "NOP zp,x",
|
|
744
|
+
0xd3: "*DCP (),y",
|
|
745
|
+
0xd4: "*NOP zp,x",
|
|
731
746
|
0xd5: "CMP zp,x",
|
|
732
747
|
0xd6: "DEC zp,x",
|
|
733
|
-
0xd7: "DCP zp,x",
|
|
748
|
+
0xd7: "*DCP zp,x",
|
|
734
749
|
0xd8: "CLD",
|
|
735
750
|
0xd9: "CMP abs,y",
|
|
736
|
-
0xda: "NOP",
|
|
737
|
-
0xdb: "DCP abs,y",
|
|
738
|
-
0xdc: "NOP abs,x",
|
|
751
|
+
0xda: "*NOP",
|
|
752
|
+
0xdb: "*DCP abs,y",
|
|
753
|
+
0xdc: "*NOP abs,x",
|
|
739
754
|
0xdd: "CMP abs,x",
|
|
740
755
|
0xde: "DEC abs,x",
|
|
741
|
-
0xdf: "DCP abs,x",
|
|
756
|
+
0xdf: "*DCP abs,x",
|
|
742
757
|
0xe0: "CPX imm",
|
|
743
758
|
0xe1: "SBC (,x)",
|
|
744
|
-
0xe2: "NOP imm",
|
|
745
|
-
0xe3: "ISB (,x)",
|
|
759
|
+
0xe2: "*NOP imm",
|
|
760
|
+
0xe3: "*ISB (,x)",
|
|
746
761
|
0xe4: "CPX zp",
|
|
747
762
|
0xe5: "SBC zp",
|
|
748
763
|
0xe6: "INC zp",
|
|
749
|
-
0xe7: "ISB zp",
|
|
764
|
+
0xe7: "*ISB zp",
|
|
750
765
|
0xe8: "INX",
|
|
751
766
|
0xe9: "SBC imm",
|
|
752
767
|
0xea: "NOP",
|
|
753
|
-
0xeb: "SBC imm",
|
|
768
|
+
0xeb: "*SBC imm",
|
|
754
769
|
0xec: "CPX abs",
|
|
755
770
|
0xed: "SBC abs",
|
|
756
771
|
0xee: "INC abs",
|
|
757
|
-
0xef: "ISB abs",
|
|
772
|
+
0xef: "*ISB abs",
|
|
758
773
|
0xf0: "BEQ branch",
|
|
759
774
|
0xf1: "SBC (),y",
|
|
760
|
-
0xf3: "ISB (),y",
|
|
761
|
-
0xf4: "NOP
|
|
775
|
+
0xf3: "*ISB (),y",
|
|
776
|
+
0xf4: "*NOP zp,x",
|
|
762
777
|
0xf5: "SBC zp,x",
|
|
763
778
|
0xf6: "INC zp,x",
|
|
764
|
-
0xf7: "ISB zp,x",
|
|
779
|
+
0xf7: "*ISB zp,x",
|
|
765
780
|
0xf8: "SED",
|
|
766
781
|
0xf9: "SBC abs,y",
|
|
767
|
-
0xfa: "NOP",
|
|
768
|
-
0xfb: "ISB abs,y",
|
|
769
|
-
0xfc: "NOP abs,x",
|
|
782
|
+
0xfa: "*NOP",
|
|
783
|
+
0xfb: "*ISB abs,y",
|
|
784
|
+
0xfc: "*NOP abs,x",
|
|
770
785
|
0xfd: "SBC abs,x",
|
|
771
786
|
0xfe: "INC abs,x",
|
|
772
|
-
0xff: "ISB abs,x",
|
|
773
|
-
};
|
|
787
|
+
0xff: "*ISB abs,x",
|
|
788
|
+
});
|
|
774
789
|
|
|
775
|
-
const opcodes65c12 = {
|
|
790
|
+
const { opcodes: opcodes65c12, undocumented: undocumented65c12 } = splitUndocumented({
|
|
776
791
|
0x00: "BRK",
|
|
777
792
|
0x01: "ORA (,x)",
|
|
778
793
|
0x04: "TSB zp",
|
|
@@ -952,9 +967,9 @@ const opcodes65c12 = {
|
|
|
952
967
|
0xfa: "PLX",
|
|
953
968
|
0xfd: "SBC abs,x",
|
|
954
969
|
0xfe: "INC abs,x",
|
|
955
|
-
};
|
|
970
|
+
});
|
|
956
971
|
|
|
957
|
-
const opcodes65c02 = {
|
|
972
|
+
const { opcodes: opcodes65c02, undocumented: undocumented65c02 } = splitUndocumented({
|
|
958
973
|
...opcodes65c12,
|
|
959
974
|
0x07: "RMB0 zp",
|
|
960
975
|
0x17: "RMB1 zp",
|
|
@@ -988,12 +1003,20 @@ const opcodes65c02 = {
|
|
|
988
1003
|
0xdf: "BBS5 zp,branch",
|
|
989
1004
|
0xef: "BBS6 zp,branch",
|
|
990
1005
|
0xff: "BBS7 zp,branch",
|
|
991
|
-
};
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
const PrevInstructionWindow = 64;
|
|
992
1009
|
|
|
993
1010
|
class Disassemble6502 {
|
|
994
|
-
constructor(cpu, opcodes) {
|
|
1011
|
+
constructor(cpu, opcodes, undocumented) {
|
|
995
1012
|
this.cpu = cpu;
|
|
996
1013
|
this.opcodes = opcodes;
|
|
1014
|
+
this.undocumented = undocumented;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
isDocumented(addr) {
|
|
1018
|
+
const op = this.cpu.peekmem(addr);
|
|
1019
|
+
return op in this.opcodes && !this.undocumented.has(op);
|
|
997
1020
|
}
|
|
998
1021
|
|
|
999
1022
|
disassemble(addr, plain) {
|
|
@@ -1003,13 +1026,15 @@ class Disassemble6502 {
|
|
|
1003
1026
|
formatAddr = hexword;
|
|
1004
1027
|
formatJumpAddr = hexword;
|
|
1005
1028
|
}
|
|
1006
|
-
const
|
|
1029
|
+
const opcodeByte = this.cpu.peekmem(addr);
|
|
1030
|
+
const opcode = this.opcodes[opcodeByte];
|
|
1007
1031
|
if (!opcode) {
|
|
1008
1032
|
return ["???", addr + 1];
|
|
1009
1033
|
}
|
|
1010
|
-
const
|
|
1034
|
+
const [mnemonic, ...rest] = opcode.split(" ");
|
|
1035
|
+
const split = [(this.undocumented.has(opcodeByte) ? "*" : "") + mnemonic, ...rest];
|
|
1011
1036
|
if (!split[1]) {
|
|
1012
|
-
return [
|
|
1037
|
+
return [split[0], addr + 1];
|
|
1013
1038
|
}
|
|
1014
1039
|
let param = split[1] || "";
|
|
1015
1040
|
let suffix = "";
|
|
@@ -1034,6 +1059,14 @@ class Disassemble6502 {
|
|
|
1034
1059
|
}
|
|
1035
1060
|
case "zp":
|
|
1036
1061
|
return [`${split[0]} $${hexbyte(this.cpu.peekmem(addr + 1))}${suffix}`, addr + 2];
|
|
1062
|
+
case "zp,branch": {
|
|
1063
|
+
const destAddr = addr + signExtend(this.cpu.peekmem(addr + 2)) + 3;
|
|
1064
|
+
return [
|
|
1065
|
+
`${split[0]} $${hexbyte(this.cpu.peekmem(addr + 1))}, $${formatJumpAddr(destAddr)}`,
|
|
1066
|
+
addr + 3,
|
|
1067
|
+
destAddr,
|
|
1068
|
+
];
|
|
1069
|
+
}
|
|
1037
1070
|
case "(,x)":
|
|
1038
1071
|
return [`${split[0]} ($${hexbyte(this.cpu.peekmem(addr + 1))}, X)${suffix}`, addr + 2];
|
|
1039
1072
|
case "()": {
|
|
@@ -1056,11 +1089,60 @@ class Disassemble6502 {
|
|
|
1056
1089
|
return [`${split[0]} ($${formatJumpAddr(destAddr)},x)${suffix}`, addr + 3, indDest];
|
|
1057
1090
|
}
|
|
1058
1091
|
}
|
|
1059
|
-
return [
|
|
1092
|
+
return [split.join(" "), addr + 1];
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
nextInstruction(address) {
|
|
1096
|
+
return this.disassemble(address)[1] & 0xffff;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* Guesses the address of the instruction before the given one by scoring
|
|
1101
|
+
* each run of instructions in the preceding window that lands exactly on
|
|
1102
|
+
* it: a point per "common" instruction (loads, stores, branches, compares,
|
|
1103
|
+
* arithmetic and carry-set/clear that avoid unusual indexing modes) and a
|
|
1104
|
+
* large boost for a run that passes through pc. Runs through undocumented
|
|
1105
|
+
* opcodes are discarded. The highest-scoring run's last instruction wins;
|
|
1106
|
+
* any run beats none, since a wrong parse resynchronises within a few
|
|
1107
|
+
* instructions and so nearly always shares the true stream's last one.
|
|
1108
|
+
* Good test cases:
|
|
1109
|
+
* Repton 2 @ 2cbb
|
|
1110
|
+
* MOS @ cfc8, and the unrolled STA abs,X screen clear @ cc63
|
|
1111
|
+
* also, just starting from the back of ROM and going up...
|
|
1112
|
+
*/
|
|
1113
|
+
prevInstruction(address, pc) {
|
|
1114
|
+
const commonInstructions =
|
|
1115
|
+
/(RTS|B..|JMP|JSR|LD[AXY]|ST[AXY]|TA[XY]|T[XY]A|AD[DC]|SUB|SBC|CLC|SEC|CMP|EOR|ORA|AND|INC|DEC).*/;
|
|
1116
|
+
const uncommonInstructions = /.*,\s*([XY]|X\))$/;
|
|
1117
|
+
|
|
1118
|
+
address &= 0xffff;
|
|
1119
|
+
let bestAddr = address - 1;
|
|
1120
|
+
let bestScore = -1;
|
|
1121
|
+
for (let startingPoint = address - PrevInstructionWindow; startingPoint !== address; startingPoint++) {
|
|
1122
|
+
let score = 0;
|
|
1123
|
+
let addr = startingPoint & 0xffff;
|
|
1124
|
+
while (addr < address) {
|
|
1125
|
+
if (!this.isDocumented(addr)) break;
|
|
1126
|
+
const result = this.disassemble(addr);
|
|
1127
|
+
if (addr === pc) score += 10;
|
|
1128
|
+
if (result[0].match(commonInstructions) && !result[0].match(uncommonInstructions)) {
|
|
1129
|
+
score++;
|
|
1130
|
+
}
|
|
1131
|
+
if (result[1] === address) {
|
|
1132
|
+
if (score > bestScore) {
|
|
1133
|
+
bestScore = score;
|
|
1134
|
+
bestAddr = addr;
|
|
1135
|
+
}
|
|
1136
|
+
break;
|
|
1137
|
+
}
|
|
1138
|
+
addr = result[1];
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return bestAddr & 0xffff;
|
|
1060
1142
|
}
|
|
1061
1143
|
}
|
|
1062
1144
|
|
|
1063
|
-
function makeCpuFunctions(cpu, opcodes, is65c12, cycleAccurate = true) {
|
|
1145
|
+
function makeCpuFunctions(cpu, opcodes, undocumented, is65c12, cycleAccurate = true) {
|
|
1064
1146
|
function getInstruction(opcodeString, needsReg) {
|
|
1065
1147
|
const split = opcodeString.split(" ");
|
|
1066
1148
|
const opcode = split[0];
|
|
@@ -1097,7 +1179,6 @@ function makeCpuFunctions(cpu, opcodes, is65c12, cycleAccurate = true) {
|
|
|
1097
1179
|
return ig.render();
|
|
1098
1180
|
|
|
1099
1181
|
case "zp":
|
|
1100
|
-
case "zpx": // Seems to be enough to keep tests happy, but needs investigation.
|
|
1101
1182
|
case "zp,x":
|
|
1102
1183
|
case "zp,y":
|
|
1103
1184
|
if (arg === "zp") {
|
|
@@ -1409,7 +1490,7 @@ ${indent}`)
|
|
|
1409
1490
|
Runner.prototype.run = generate6502JumpTable();
|
|
1410
1491
|
|
|
1411
1492
|
return {
|
|
1412
|
-
disassembler: new Disassemble6502(cpu, opcodes),
|
|
1493
|
+
disassembler: new Disassemble6502(cpu, opcodes, undocumented),
|
|
1413
1494
|
runInstruction: new Runner(),
|
|
1414
1495
|
opcodes: opcodes,
|
|
1415
1496
|
getInstruction: getInstruction,
|
|
@@ -1417,13 +1498,13 @@ ${indent}`)
|
|
|
1417
1498
|
}
|
|
1418
1499
|
|
|
1419
1500
|
export function Cpu6502(cpu, { cycleAccurate = true } = {}) {
|
|
1420
|
-
return makeCpuFunctions(cpu, opcodes6502, false, cycleAccurate);
|
|
1501
|
+
return makeCpuFunctions(cpu, opcodes6502, undocumented6502, false, cycleAccurate);
|
|
1421
1502
|
}
|
|
1422
1503
|
|
|
1423
1504
|
export function Cpu65c12(cpu, { cycleAccurate = true } = {}) {
|
|
1424
|
-
return makeCpuFunctions(cpu, opcodes65c12, true, cycleAccurate);
|
|
1505
|
+
return makeCpuFunctions(cpu, opcodes65c12, undocumented65c12, true, cycleAccurate);
|
|
1425
1506
|
}
|
|
1426
1507
|
|
|
1427
1508
|
export function Cpu65c02(cpu, { cycleAccurate = true } = {}) {
|
|
1428
|
-
return makeCpuFunctions(cpu, opcodes65c02, true, cycleAccurate);
|
|
1509
|
+
return makeCpuFunctions(cpu, opcodes65c02, undocumented65c02, true, cycleAccurate);
|
|
1429
1510
|
}
|
package/src/jsbeeb.css
CHANGED
|
@@ -560,6 +560,39 @@ small {
|
|
|
560
560
|
flex: none;
|
|
561
561
|
}
|
|
562
562
|
|
|
563
|
+
#header-bar .navbar-brand img {
|
|
564
|
+
display: block;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
#header-bar [data-icon] > svg {
|
|
568
|
+
width: 1.15em;
|
|
569
|
+
height: 1.15em;
|
|
570
|
+
vertical-align: -0.2em;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/* Shrunk, the menu list wraps onto a second row; the promo yields instead, by truncating. */
|
|
574
|
+
#header-bar .navbar-nav {
|
|
575
|
+
flex-shrink: 0;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
#header-bar .navbar-text {
|
|
579
|
+
flex: 0 1 auto;
|
|
580
|
+
min-width: 0;
|
|
581
|
+
white-space: nowrap;
|
|
582
|
+
overflow: hidden;
|
|
583
|
+
text-overflow: ellipsis;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
#paste-form {
|
|
587
|
+
flex: 1 0 6rem;
|
|
588
|
+
min-width: 0;
|
|
589
|
+
max-width: 14rem;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
#paste-text {
|
|
593
|
+
min-width: 0;
|
|
594
|
+
}
|
|
595
|
+
|
|
563
596
|
/* The only item in the header with no bound on its width, so it is the one that yields. */
|
|
564
597
|
#disc-name {
|
|
565
598
|
flex: 1;
|
package/src/main.js
CHANGED
|
@@ -9,6 +9,7 @@ import * as utils_atom from "./utils_atom.js";
|
|
|
9
9
|
import { GamePad } from "./gamepads.js";
|
|
10
10
|
import { initialise as electron } from "./app/electron.js";
|
|
11
11
|
import { AudioHandler } from "./web/audio-handler.js";
|
|
12
|
+
import { installIcons } from "./web/icons.js";
|
|
12
13
|
import { QuickSettings } from "./web/quick-settings.js";
|
|
13
14
|
import { toast } from "./web/toast.js";
|
|
14
15
|
import { BuiltInImages, MediaLoader } from "./web/media-loader.js";
|
|
@@ -37,6 +38,8 @@ import { DiscVisualiser } from "./web/disc-visualiser.js";
|
|
|
37
38
|
import { downloadDriveData } from "./dom-utils.js";
|
|
38
39
|
import { parseMediaParams, processAutobootParams, processDriveTrackParams, processInputParams } from "./url-params.js";
|
|
39
40
|
|
|
41
|
+
installIcons();
|
|
42
|
+
|
|
40
43
|
// ------------------------------------------------------------------------
|
|
41
44
|
// What the URL asked for.
|
|
42
45
|
// ------------------------------------------------------------------------
|
|
@@ -196,6 +199,7 @@ const keys = new KeyboardSetup({
|
|
|
196
199
|
openPrinter: () => frontPanel.checkPrinterWindow(),
|
|
197
200
|
pause: () => loop.stop(false),
|
|
198
201
|
resume: () => loop.go(),
|
|
202
|
+
paste: (text) => keys.sendRawKeyboard(autoBoot.stringToMachineKeys(text), true),
|
|
199
203
|
onAnyKeyDown: () => {
|
|
200
204
|
audioHandler.tryResume();
|
|
201
205
|
inputs.ensureMicrophoneRunning();
|
|
@@ -332,12 +336,7 @@ settings.wire({ audioHandler, display, layout, quickSettings, machine, keys, inp
|
|
|
332
336
|
|
|
333
337
|
for (const el of document.querySelectorAll(".initially-hidden")) el.classList.remove("initially-hidden");
|
|
334
338
|
|
|
335
|
-
|
|
336
|
-
pastetext.closest("form").addEventListener("submit", (event) => event.preventDefault());
|
|
337
|
-
pastetext.addEventListener("paste", function (event) {
|
|
338
|
-
const text = event.clipboardData.getData("text/plain");
|
|
339
|
-
keys.sendRawKeyboard(autoBoot.stringToMachineKeys(text), true);
|
|
340
|
-
});
|
|
339
|
+
document.getElementById("paste-form").addEventListener("submit", (event) => event.preventDefault());
|
|
341
340
|
|
|
342
341
|
window.addEventListener("blur", function () {
|
|
343
342
|
keys.clearKeys();
|
package/src/models.js
CHANGED
|
@@ -19,9 +19,23 @@ const CpuModel = Object.freeze({
|
|
|
19
19
|
* to any number of machines without one session's settings leaking into the next.
|
|
20
20
|
*/
|
|
21
21
|
class Model {
|
|
22
|
-
constructor({
|
|
22
|
+
constructor({
|
|
23
|
+
name,
|
|
24
|
+
shortName = name,
|
|
25
|
+
synonyms,
|
|
26
|
+
os,
|
|
27
|
+
cpuModel,
|
|
28
|
+
isMaster,
|
|
29
|
+
isAtom,
|
|
30
|
+
swram,
|
|
31
|
+
fdc,
|
|
32
|
+
cmosOverride,
|
|
33
|
+
banks,
|
|
34
|
+
clockMhz,
|
|
35
|
+
} = {}) {
|
|
23
36
|
if (!(clockMhz > 0)) throw new Error(`Model ${name} has no clock speed`);
|
|
24
37
|
this.name = name;
|
|
38
|
+
this.shortName = shortName;
|
|
25
39
|
this.synonyms = synonyms;
|
|
26
40
|
this.os = os;
|
|
27
41
|
this.clockMhz = clockMhz;
|
|
@@ -79,6 +93,7 @@ function pickDfs(cmos) {
|
|
|
79
93
|
function atomModel({ name, synonyms, os, banks }) {
|
|
80
94
|
return new Model({
|
|
81
95
|
name,
|
|
96
|
+
shortName: "Atom",
|
|
82
97
|
synonyms,
|
|
83
98
|
os,
|
|
84
99
|
cpuModel: CpuModel.MOS6502,
|
|
@@ -132,6 +147,7 @@ const masterSwram = [
|
|
|
132
147
|
export const allModels = [
|
|
133
148
|
new Model({
|
|
134
149
|
name: "BBC B with 8271 (DFS 1.2)",
|
|
150
|
+
shortName: "BBC B",
|
|
135
151
|
synonyms: ["B-DFS1.2", "BBC B with DFS 1.2"],
|
|
136
152
|
os: ["os.rom", "BASIC.ROM", "b/DFS-1.2.rom"],
|
|
137
153
|
cpuModel: CpuModel.MOS6502,
|
|
@@ -142,6 +158,7 @@ export const allModels = [
|
|
|
142
158
|
}),
|
|
143
159
|
new Model({
|
|
144
160
|
name: "BBC B with 8271 (DFS 0.9)",
|
|
161
|
+
shortName: "BBC B",
|
|
145
162
|
synonyms: ["B-DFS0.9", "B", "BBC B with DFS 0.9"],
|
|
146
163
|
os: ["os.rom", "BASIC.ROM", "b/DFS-0.9.rom"],
|
|
147
164
|
cpuModel: CpuModel.MOS6502,
|
|
@@ -152,6 +169,7 @@ export const allModels = [
|
|
|
152
169
|
}),
|
|
153
170
|
new Model({
|
|
154
171
|
name: "BBC B with 1770 (DFS)",
|
|
172
|
+
shortName: "BBC B",
|
|
155
173
|
synonyms: ["B1770"],
|
|
156
174
|
os: ["os.rom", "BASIC.ROM", "b1770/dfs1770.rom", "b1770/zADFS.ROM"],
|
|
157
175
|
cpuModel: CpuModel.MOS6502,
|
|
@@ -163,6 +181,7 @@ export const allModels = [
|
|
|
163
181
|
// putting ADFS in a higher ROM slot gives it priority
|
|
164
182
|
new Model({
|
|
165
183
|
name: "BBC B with 1770 (ADFS)",
|
|
184
|
+
shortName: "BBC B",
|
|
166
185
|
synonyms: ["B1770A"],
|
|
167
186
|
os: ["os.rom", "BASIC.ROM", "b1770/zADFS.ROM", "b1770/dfs1770.rom"],
|
|
168
187
|
cpuModel: CpuModel.MOS6502,
|
|
@@ -173,6 +192,7 @@ export const allModels = [
|
|
|
173
192
|
}),
|
|
174
193
|
new Model({
|
|
175
194
|
name: "BBC Master 128 (DFS)",
|
|
195
|
+
shortName: "Master 128",
|
|
176
196
|
synonyms: ["Master"],
|
|
177
197
|
os: ["master/mos3.20"],
|
|
178
198
|
cpuModel: CpuModel.CMOS65C12,
|
|
@@ -184,6 +204,7 @@ export const allModels = [
|
|
|
184
204
|
}),
|
|
185
205
|
new Model({
|
|
186
206
|
name: "BBC Master 128 (ADFS)",
|
|
207
|
+
shortName: "Master 128",
|
|
187
208
|
synonyms: ["MasterADFS"],
|
|
188
209
|
os: ["master/mos3.20"],
|
|
189
210
|
cpuModel: CpuModel.CMOS65C12,
|
|
@@ -195,6 +216,7 @@ export const allModels = [
|
|
|
195
216
|
}),
|
|
196
217
|
new Model({
|
|
197
218
|
name: "BBC Master 128 (ANFS)",
|
|
219
|
+
shortName: "Master 128",
|
|
198
220
|
synonyms: ["MasterANFS"],
|
|
199
221
|
os: ["master/mos3.20"],
|
|
200
222
|
cpuModel: CpuModel.CMOS65C12,
|
package/src/web/config.js
CHANGED
|
@@ -216,6 +216,9 @@ export class Config extends EventTarget {
|
|
|
216
216
|
setModel(modelName) {
|
|
217
217
|
this.model = findModel(modelName);
|
|
218
218
|
for (const el of document.querySelectorAll(".bbc-model")) el.textContent = this.model.name;
|
|
219
|
+
for (const el of document.querySelectorAll(".bbc-model-short")) el.textContent = this.model.shortName;
|
|
220
|
+
const settingsLink = document.getElementById("model-settings");
|
|
221
|
+
if (settingsLink) settingsLink.title = `${this.model.name}: emulation settings`;
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
setKeyLayout(keyLayout) {
|
package/src/web/debug.js
CHANGED
|
@@ -89,48 +89,6 @@ class MemoryView {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
/**
|
|
93
|
-
* Guesses the address of the instruction before the given one by scoring each
|
|
94
|
-
* possible run of instructions leading up to it: a point per "common"
|
|
95
|
-
* instruction (loads, stores, branches, compares, arithmetic and
|
|
96
|
-
* carry-set/clear that avoid unusual indexing modes) and a large boost for a
|
|
97
|
-
* run that passes through the current program counter. The highest-scoring
|
|
98
|
-
* run wins and its last instruction is the answer.
|
|
99
|
-
* Good test cases:
|
|
100
|
-
* Repton 2 @ 2cbb
|
|
101
|
-
* MOS @ cfc8
|
|
102
|
-
* also, just starting from the back of ROM and going up...
|
|
103
|
-
*/
|
|
104
|
-
export function prevInstruction(disassemble, pc, address) {
|
|
105
|
-
const commonInstructions =
|
|
106
|
-
/(RTS|B..|JMP|JSR|LD[AXY]|ST[AXY]|TA[XY]|T[XY]A|AD[DC]|SUB|SBC|CLC|SEC|CMP|EOR|ORR|AND|INC|DEC).*/;
|
|
107
|
-
const uncommonInstrucions = /.*,\s*([XY]|X\))$/;
|
|
108
|
-
|
|
109
|
-
address &= 0xffff;
|
|
110
|
-
let bestAddr = address - 1;
|
|
111
|
-
let bestScore = 0;
|
|
112
|
-
for (let startingPoint = address - 20; startingPoint !== address; startingPoint++) {
|
|
113
|
-
let score = 0;
|
|
114
|
-
let addr = startingPoint & 0xffff;
|
|
115
|
-
while (addr < address) {
|
|
116
|
-
const result = disassemble(addr);
|
|
117
|
-
if (addr === pc) score += 10;
|
|
118
|
-
if (result[0].match(commonInstructions) && !result[0].match(uncommonInstrucions)) {
|
|
119
|
-
score++;
|
|
120
|
-
}
|
|
121
|
-
if (result[1] === address) {
|
|
122
|
-
if (score > bestScore) {
|
|
123
|
-
bestScore = score;
|
|
124
|
-
bestAddr = addr;
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
addr = result[1];
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return bestAddr;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
92
|
export class Debugger {
|
|
135
93
|
constructor() {
|
|
136
94
|
this.patchInstructions = new Map();
|
|
@@ -424,7 +382,7 @@ export class Debugger {
|
|
|
424
382
|
}
|
|
425
383
|
|
|
426
384
|
prevInstruction(address) {
|
|
427
|
-
return
|
|
385
|
+
return this.cpu.disassembler.prevInstruction(address, this.cpu.pc);
|
|
428
386
|
}
|
|
429
387
|
|
|
430
388
|
updatePrevMem() {
|
|
@@ -483,7 +441,7 @@ export class Debugger {
|
|
|
483
441
|
}
|
|
484
442
|
|
|
485
443
|
nextInstruction(address) {
|
|
486
|
-
return this.
|
|
444
|
+
return this.cpu.disassembler.nextInstruction(address);
|
|
487
445
|
}
|
|
488
446
|
|
|
489
447
|
instrClick(e) {
|
package/src/web/icons.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import arrowCounterclockwise from "bootstrap-icons/icons/arrow-counterclockwise.svg?raw";
|
|
2
|
+
import cassette from "bootstrap-icons/icons/cassette.svg?raw";
|
|
3
|
+
import clockHistory from "bootstrap-icons/icons/clock-history.svg?raw";
|
|
4
|
+
import display from "bootstrap-icons/icons/display.svg?raw";
|
|
5
|
+
import floppy from "bootstrap-icons/icons/floppy.svg?raw";
|
|
6
|
+
import gear from "bootstrap-icons/icons/gear.svg?raw";
|
|
7
|
+
import headphones from "bootstrap-icons/icons/headphones.svg?raw";
|
|
8
|
+
import pauseFill from "bootstrap-icons/icons/pause-fill.svg?raw";
|
|
9
|
+
import playFill from "bootstrap-icons/icons/play-fill.svg?raw";
|
|
10
|
+
import soundwave from "bootstrap-icons/icons/soundwave.svg?raw";
|
|
11
|
+
import stars from "bootstrap-icons/icons/stars.svg?raw";
|
|
12
|
+
import threeDots from "bootstrap-icons/icons/three-dots.svg?raw";
|
|
13
|
+
import tv from "bootstrap-icons/icons/tv.svg?raw";
|
|
14
|
+
import volumeUp from "bootstrap-icons/icons/volume-up.svg?raw";
|
|
15
|
+
|
|
16
|
+
/** The Bootstrap Icons the page uses, by their upstream names; nothing else of the set is bundled. */
|
|
17
|
+
const Icons = {
|
|
18
|
+
"arrow-counterclockwise": arrowCounterclockwise,
|
|
19
|
+
cassette,
|
|
20
|
+
"clock-history": clockHistory,
|
|
21
|
+
display,
|
|
22
|
+
floppy,
|
|
23
|
+
gear,
|
|
24
|
+
headphones,
|
|
25
|
+
"pause-fill": pauseFill,
|
|
26
|
+
"play-fill": playFill,
|
|
27
|
+
soundwave,
|
|
28
|
+
stars,
|
|
29
|
+
"three-dots": threeDots,
|
|
30
|
+
tv,
|
|
31
|
+
"volume-up": volumeUp,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const IconNames = Object.freeze(Object.keys(Icons));
|
|
35
|
+
|
|
36
|
+
/** Puts the named SVG inside every `[data-icon]` element under `root`; an unknown name leaves them all untouched. */
|
|
37
|
+
export function installIcons(root = document) {
|
|
38
|
+
const placeholders = [...root.querySelectorAll("[data-icon]")];
|
|
39
|
+
const unknown = placeholders.find((el) => !(el.dataset.icon in Icons));
|
|
40
|
+
if (unknown) throw new Error(`No icon named ${unknown.dataset.icon}`);
|
|
41
|
+
for (const el of placeholders) el.innerHTML = Icons[el.dataset.icon];
|
|
42
|
+
}
|
|
@@ -2,19 +2,22 @@ import * as utils from "../utils.js";
|
|
|
2
2
|
import { Keyboard } from "../keyboard.js";
|
|
3
3
|
import { showNotice } from "./reporting.js";
|
|
4
4
|
|
|
5
|
+
const PasteBoxId = "paste-text";
|
|
6
|
+
const TypingTargets = 'input, textarea, select, [contenteditable]:not([contenteditable="false"])';
|
|
7
|
+
|
|
5
8
|
/** The page's keyboard: the emulated one and the browser shortcuts around it. */
|
|
6
9
|
export class KeyboardSetup {
|
|
7
10
|
/**
|
|
8
11
|
* @param {object} opts
|
|
9
12
|
* @param {object} opts.actions what each shortcut does, supplied late-bound:
|
|
10
13
|
* enterDebugger, reload, toggleFast, openRewind, openPrinter,
|
|
11
|
-
* pause, resume, onAnyKeyDown
|
|
14
|
+
* pause, resume, paste, onAnyKeyDown
|
|
12
15
|
* @param {import("./accessibility-switches.js").AccessibilitySwitches} opts.accessibilitySwitches
|
|
13
16
|
*/
|
|
14
17
|
constructor({ actions, accessibilitySwitches, processor, dbgr, keyLayout }) {
|
|
15
18
|
const keyboard = (this.keyboard = new Keyboard({
|
|
16
19
|
processor,
|
|
17
|
-
inputEnabledFunction: () => document.activeElement && document.activeElement.id ===
|
|
20
|
+
inputEnabledFunction: () => document.activeElement && document.activeElement.id === PasteBoxId,
|
|
18
21
|
keyLayout,
|
|
19
22
|
dbgr,
|
|
20
23
|
}));
|
|
@@ -62,6 +65,12 @@ export class KeyboardSetup {
|
|
|
62
65
|
});
|
|
63
66
|
document.addEventListener("keypress", (evt) => keyboard.keyPress(evt));
|
|
64
67
|
document.addEventListener("keyup", (evt) => keyboard.keyUp(evt));
|
|
68
|
+
document.addEventListener("paste", (evt) => {
|
|
69
|
+
const target = document.activeElement;
|
|
70
|
+
if (target && target.id !== PasteBoxId && target.matches(TypingTargets)) return;
|
|
71
|
+
const text = evt.clipboardData?.getData("text/plain");
|
|
72
|
+
if (text) actions.paste(text);
|
|
73
|
+
});
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
sendRawKeyboard(keysToSend, checkCapsAndShiftLocks) {
|