initial commit
[dosdemo] / src / dos / keyb.c
1 /*
2 DOS interrupt-based keyboard driver.
3 Copyright (C) 2013  John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License  for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with the program. If not, see <http://www.gnu.org/licenses/>
17 */
18 #define KEYB_C_
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <conio.h>
24 #include <dos.h>
25 #include <i86.h>
26 #include "keyb.h"
27 #include "scancode.h"
28
29 #define KB_INTR         0x9
30 #define KB_PORT         0x60
31
32 #define PIC1_CMD_PORT   0x20
33 #define OCW2_EOI                (1 << 5)
34
35 #define DONE_INIT       (prev_handler)
36
37 static void __interrupt __far kbintr();
38
39 static void (__interrupt __far *prev_handler)();
40
41 static int *buffer;
42 static int buffer_size, buf_ridx, buf_widx;
43 static int last_key;
44
45 static unsigned int num_pressed;
46 static unsigned char keystate[256];
47
48 #define ADVANCE(x)      ((x) = ((x) + 1) % buffer_size)
49
50 int kb_init(int bufsz)
51 {
52         if(DONE_INIT) {
53                 fprintf(stderr, "keyboard driver already initialized!\n");
54                 return 0;
55         }
56
57         buffer_size = bufsz;
58         if(buffer_size && !(buffer = malloc(buffer_size * sizeof *buffer))) {
59                 fprintf(stderr, "failed to allocate input buffer, continuing without\n");
60                 buffer_size = 0;
61         }
62         buf_ridx = buf_widx = 0;
63         last_key = -1;
64
65         memset(keystate, 0, sizeof keystate);
66         num_pressed = 0;
67
68         /* set our interrupt handler */
69         _disable();
70         prev_handler = _dos_getvect(KB_INTR);
71         _dos_setvect(KB_INTR, kbintr);
72         _enable();
73
74         return 0;
75 }
76
77 void kb_shutdown(void)
78 {
79         if(!DONE_INIT) {
80                 return;
81         }
82
83         /* restore the original interrupt handler */
84         _disable();
85         _dos_setvect(KB_INTR, prev_handler);
86         _enable();
87
88         free(buffer);
89 }
90
91 int kb_isdown(int key)
92 {
93         switch(key) {
94         case KB_ANY:
95                 return num_pressed;
96
97         case KB_ALT:
98                 return keystate[KB_LALT] + keystate[KB_RALT];
99
100         case KB_CTRL:
101                 return keystate[KB_LCTRL] + keystate[KB_RCTRL];
102         }
103         return keystate[key];
104 }
105
106 void kb_wait(void)
107 {
108         int key;
109         while((key = kb_getkey()) == -1) {
110                 /* put the processor to sleep while waiting for keypresses, but first
111                  * make sure interrupts are enabled, or we'll sleep forever
112                  */
113                 __asm {
114                         sti
115                         hlt
116                 }
117         }
118         kb_putback(key);
119 }
120
121 int kb_getkey(void)
122 {
123         int res;
124
125         if(buffer) {
126                 if(buf_ridx == buf_widx) {
127                         return -1;
128                 }
129                 res = buffer[buf_ridx];
130                 ADVANCE(buf_ridx);
131         } else {
132                 res = last_key;
133                 last_key = -1;
134         }
135         return res;
136 }
137
138 void kb_putback(int key)
139 {
140         if(buffer) {
141                 /* go back a place */
142                 if(--buf_ridx < 0) {
143                         buf_ridx += buffer_size;
144                 }
145
146                 /* if the write end hasn't caught up with us, go back one place
147                  * and put it there, otherwise just overwrite the oldest key which
148                  * is right where we were.
149                  */
150                 if(buf_ridx == buf_widx) {
151                         ADVANCE(buf_ridx);
152                 }
153
154                 buffer[buf_ridx] = key;
155         } else {
156                 last_key = key;
157         }
158 }
159
160 static void __interrupt __far kbintr()
161 {
162         unsigned char code;
163         int key, press;
164
165         code = inp(KB_PORT);
166
167         if(code >= 128) {
168                 press = 0;
169                 code -= 128;
170
171                 if(num_pressed > 0) {
172                         num_pressed--;
173                 }
174         } else {
175                 press = 1;
176
177                 num_pressed++;
178         }
179
180         key = scantbl[code];
181
182         if(press) {
183                 /* append to buffer */
184                 last_key = key;
185                 if(buffer_size > 0) {
186                         buffer[buf_widx] = key;
187                         ADVANCE(buf_widx);
188                         /* if the write end overtook the read end, advance the read end
189                          * too, to discard the oldest keypress from the buffer
190                          */
191                         if(buf_widx == buf_ridx) {
192                                 ADVANCE(buf_ridx);
193                         }
194                 }
195         }
196
197         /* and update keystate table */
198         keystate[key] = press;
199
200         outp(PIC1_CMD_PORT, OCW2_EOI);  /* send end-of-interrupt */
201 }