1 module dcpu16.emulator.devices.keyboard;
2 
3 import dcpu16.emulator.idevice;
4 import dcpu16.emulator;
5 
6 class Keyboard : IDevice
7 {
8     override uint id() const pure { return 0x30cf7406; };
9     override uint manufacturer() const pure { return 0; };
10     override ushort ver() const pure { return 1; };
11 
12     private Computer comp;
13     private ubyte[] buf;
14     private size_t maxBufLength;
15     private CheckKeyIsPressed checkKeyPressed;
16     private ushort interruptsMsg;
17     private bool enableMemMapping;
18 
19     alias CheckKeyIsPressed = bool delegate(ubyte ascii_or_enum_Key);
20 
21     this(Computer c, CheckKeyIsPressed dg, bool enable0x9000Mapping = true, size_t keyBufferLength = 8)
22     {
23         comp = c;
24         checkKeyPressed = dg;
25         enableMemMapping = enable0x9000Mapping;
26         maxBufLength = keyBufferLength;
27     }
28 
29     override void reset()
30     {
31         buf = null;
32     }
33 
34     override void handleHardwareInterrupt(in Computer _unused)
35     {
36         assert(comp == _unused);
37 
38         with(InterruptActions)
39         with(comp)
40         with(cpu.regs)
41         switch(A)
42         {
43             case CLEAR_BUFFER:
44                 buf = null;
45                 return;
46 
47             case GET_NEXT:
48                 if(buf.length == 0)
49                 {
50                     comp.cpu.regs.c = 0;
51                 }
52                 else
53                 {
54                     comp.cpu.regs.c = buf[0];
55                     buf = buf[1 .. $];
56                 }
57                 return;
58 
59             case CHECK_KEY:
60                 ubyte b = comp.cpu.regs.B & ubyte.max;
61                 if(b != comp.cpu.regs.B) return;
62                 comp.cpu.regs.c = checkKeyPressed(b) ? 1 : 0;
63                 return;
64 
65             case SET_INT:
66                 interruptsMsg = comp.cpu.regs.B;
67                 return;
68 
69             default:
70                 break;
71         }
72     }
73 
74     void keyPressed(ubyte ascii_or_enum_Key)
75     {
76         assert(ascii_or_enum_Key != 0);
77         // TODO: add more checks
78 
79         // A 16-word buffer [0x9000 to 0x900e] holds the most recently input characters in a ring buffer, one word per character.
80         if(enableMemMapping)
81         {
82             static ushort mapIdx = 0x9000;
83 
84             comp.mem[mapIdx] = ascii_or_enum_Key;
85             mapIdx++;
86 
87             if(mapIdx > 0x900e)
88                 mapIdx = 0x9000;
89         }
90 
91         if(buf.length <= maxBufLength)
92             buf ~= ascii_or_enum_Key;
93 
94         if(interruptsMsg)
95             comp.cpu.addInterruptOrBurnOut(interruptsMsg);
96     }
97 }
98 
99 unittest
100 {
101     // 0x9010 holds end of buffer
102     ushort[] createRingBuff= [
103             0x7f01, 0x0000, 0x7f81, 0x0004, 0x7821, 0x9010, 0x4401, 0x9000,
104             0x8621, 0x9000, 0x8822, 0xc428, 0x07c1, 0x9010, 0x6381
105         ];
106 
107     auto comp = new Computer();
108     comp.load(createRingBuff);
109 
110     foreach(_; 0 .. 4000)
111     {
112         //~ import std.stdio;
113         //~ comp.machineState.writeln;
114         //~ comp.memDump(0x9000).writeln;
115         //~ comp.memDump(0x9010).writeln;
116         comp.cpu.step;
117     }
118 }
119 
120 enum InterruptActions : ushort
121 {
122     CLEAR_BUFFER,
123     GET_NEXT,
124     CHECK_KEY,
125     SET_INT, /// If register B is non-zero, turn on interrupts with message B. If B is zero, disable interrupts.
126 }
127 
128 enum Key : ubyte
129 {
130     none,
131     Backspace = 0x10,
132     Return,
133     Insert,
134     Delete,
135     ArrowUp = 0x80,
136     ArrowDown,
137     ArrowLeft,
138     ArrowRight,
139     Shift = 0x90,
140     Control,
141     Alt, /// Unofficial
142 }