diff options
author | Samuel Thibault <samuel.thibault@ens-lyon.org> | 2008-04-30 03:54:51 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2008-04-30 11:29:52 -0400 |
commit | f7511d5f66f01fc451747b24e79f3ada7a3af9af (patch) | |
tree | 934196c15e43077641e35286078a6753700a3e3d /drivers/accessibility/braille | |
parent | 730f412c08c13858f7681bac0a2770fbc9159fed (diff) |
Basic braille screen reader support
This adds a minimalistic braille screen reader support. This is meant to
be used by blind people e.g. on boot failures or when / cannot be mounted
etc and thus the userland screen readers can not work.
[akpm@linux-foundation.org: fix exports]
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Cc: Jiri Kosina <jikos@jikos.cz>
Cc: Dmitry Torokhov <dtor@mail.ru>
Acked-by: Alan Cox <alan@redhat.com>
Cc: Randy Dunlap <randy.dunlap@oracle.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/accessibility/braille')
-rw-r--r-- | drivers/accessibility/braille/Makefile | 1 | ||||
-rw-r--r-- | drivers/accessibility/braille/braille_console.c | 397 |
2 files changed, 398 insertions, 0 deletions
diff --git a/drivers/accessibility/braille/Makefile b/drivers/accessibility/braille/Makefile new file mode 100644 index 000000000000..2e9f16c91347 --- /dev/null +++ b/drivers/accessibility/braille/Makefile | |||
@@ -0,0 +1 @@ | |||
obj-$(CONFIG_A11Y_BRAILLE_CONSOLE) += braille_console.o | |||
diff --git a/drivers/accessibility/braille/braille_console.c b/drivers/accessibility/braille/braille_console.c new file mode 100644 index 000000000000..0a5f6b2114c5 --- /dev/null +++ b/drivers/accessibility/braille/braille_console.c | |||
@@ -0,0 +1,397 @@ | |||
1 | /* | ||
2 | * Minimalistic braille device kernel support. | ||
3 | * | ||
4 | * By default, shows console messages on the braille device. | ||
5 | * Pressing Insert switches to VC browsing. | ||
6 | * | ||
7 | * Copyright (C) Samuel Thibault <samuel.thibault@ens-lyon.org> | ||
8 | * | ||
9 | * This program is free software ; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License as published by | ||
11 | * the Free Software Foundation ; either version 2 of the License, or | ||
12 | * (at your option) any later version. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, | ||
15 | * but WITHOUT ANY WARRANTY ; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | * GNU General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with the program ; if not, write to the Free Software | ||
21 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
22 | */ | ||
23 | |||
24 | #include <linux/autoconf.h> | ||
25 | #include <linux/kernel.h> | ||
26 | #include <linux/module.h> | ||
27 | #include <linux/moduleparam.h> | ||
28 | #include <linux/console.h> | ||
29 | #include <linux/notifier.h> | ||
30 | |||
31 | #include <linux/selection.h> | ||
32 | #include <linux/vt_kern.h> | ||
33 | #include <linux/consolemap.h> | ||
34 | |||
35 | #include <linux/keyboard.h> | ||
36 | #include <linux/kbd_kern.h> | ||
37 | #include <linux/input.h> | ||
38 | |||
39 | MODULE_AUTHOR("samuel.thibault@ens-lyon.org"); | ||
40 | MODULE_DESCRIPTION("braille device"); | ||
41 | MODULE_LICENSE("GPL"); | ||
42 | |||
43 | /* | ||
44 | * Braille device support part. | ||
45 | */ | ||
46 | |||
47 | /* Emit various sounds */ | ||
48 | static int sound; | ||
49 | module_param(sound, bool, 0); | ||
50 | MODULE_PARM_DESC(sound, "emit sounds"); | ||
51 | |||
52 | static void beep(unsigned int freq) | ||
53 | { | ||
54 | if (sound) | ||
55 | kd_mksound(freq, HZ/10); | ||
56 | } | ||
57 | |||
58 | /* mini console */ | ||
59 | #define WIDTH 40 | ||
60 | #define BRAILLE_KEY KEY_INSERT | ||
61 | static u16 console_buf[WIDTH]; | ||
62 | static int console_cursor; | ||
63 | |||
64 | /* mini view of VC */ | ||
65 | static int vc_x, vc_y, lastvc_x, lastvc_y; | ||
66 | |||
67 | /* show console ? (or show VC) */ | ||
68 | static int console_show = 1; | ||
69 | /* pending newline ? */ | ||
70 | static int console_newline = 1; | ||
71 | static int lastVC = -1; | ||
72 | |||
73 | static struct console *braille_co; | ||
74 | |||
75 | /* Very VisioBraille-specific */ | ||
76 | static void braille_write(u16 *buf) | ||
77 | { | ||
78 | static u16 lastwrite[WIDTH]; | ||
79 | unsigned char data[1 + 1 + 2*WIDTH + 2 + 1], csum = 0, *c; | ||
80 | u16 out; | ||
81 | int i; | ||
82 | |||
83 | if (!braille_co) | ||
84 | return; | ||
85 | |||
86 | if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf))) | ||
87 | return; | ||
88 | memcpy(lastwrite, buf, WIDTH * sizeof(*buf)); | ||
89 | |||
90 | #define SOH 1 | ||
91 | #define STX 2 | ||
92 | #define ETX 2 | ||
93 | #define EOT 4 | ||
94 | #define ENQ 5 | ||
95 | data[0] = STX; | ||
96 | data[1] = '>'; | ||
97 | csum ^= '>'; | ||
98 | c = &data[2]; | ||
99 | for (i = 0; i < WIDTH; i++) { | ||
100 | out = buf[i]; | ||
101 | if (out >= 0x100) | ||
102 | out = '?'; | ||
103 | else if (out == 0x00) | ||
104 | out = ' '; | ||
105 | csum ^= out; | ||
106 | if (out <= 0x05) { | ||
107 | *c++ = SOH; | ||
108 | out |= 0x40; | ||
109 | } | ||
110 | *c++ = out; | ||
111 | } | ||
112 | |||
113 | if (csum <= 0x05) { | ||
114 | *c++ = SOH; | ||
115 | csum |= 0x40; | ||
116 | } | ||
117 | *c++ = csum; | ||
118 | *c++ = ETX; | ||
119 | |||
120 | braille_co->write(braille_co, data, c - data); | ||
121 | } | ||
122 | |||
123 | /* Follow the VC cursor*/ | ||
124 | static void vc_follow_cursor(struct vc_data *vc) | ||
125 | { | ||
126 | vc_x = vc->vc_x - (vc->vc_x % WIDTH); | ||
127 | vc_y = vc->vc_y; | ||
128 | lastvc_x = vc->vc_x; | ||
129 | lastvc_y = vc->vc_y; | ||
130 | } | ||
131 | |||
132 | /* Maybe the VC cursor moved, if so follow it */ | ||
133 | static void vc_maybe_cursor_moved(struct vc_data *vc) | ||
134 | { | ||
135 | if (vc->vc_x != lastvc_x || vc->vc_y != lastvc_y) | ||
136 | vc_follow_cursor(vc); | ||
137 | } | ||
138 | |||
139 | /* Show portion of VC at vc_x, vc_y */ | ||
140 | static void vc_refresh(struct vc_data *vc) | ||
141 | { | ||
142 | u16 buf[WIDTH]; | ||
143 | int i; | ||
144 | |||
145 | for (i = 0; i < WIDTH; i++) { | ||
146 | u16 glyph = screen_glyph(vc, | ||
147 | 2 * (vc_x + i) + vc_y * vc->vc_size_row); | ||
148 | buf[i] = inverse_translate(vc, glyph, 1); | ||
149 | } | ||
150 | braille_write(buf); | ||
151 | } | ||
152 | |||
153 | /* | ||
154 | * Link to keyboard | ||
155 | */ | ||
156 | |||
157 | static int keyboard_notifier_call(struct notifier_block *blk, | ||
158 | unsigned long code, void *_param) | ||
159 | { | ||
160 | struct keyboard_notifier_param *param = _param; | ||
161 | struct vc_data *vc = param->vc; | ||
162 | int ret = NOTIFY_OK; | ||
163 | |||
164 | if (!param->down) | ||
165 | return ret; | ||
166 | |||
167 | switch (code) { | ||
168 | case KBD_KEYCODE: | ||
169 | if (console_show) { | ||
170 | if (param->value == BRAILLE_KEY) { | ||
171 | console_show = 0; | ||
172 | beep(880); | ||
173 | vc_maybe_cursor_moved(vc); | ||
174 | vc_refresh(vc); | ||
175 | ret = NOTIFY_STOP; | ||
176 | } | ||
177 | } else { | ||
178 | ret = NOTIFY_STOP; | ||
179 | switch (param->value) { | ||
180 | case KEY_INSERT: | ||
181 | beep(440); | ||
182 | console_show = 1; | ||
183 | lastVC = -1; | ||
184 | braille_write(console_buf); | ||
185 | break; | ||
186 | case KEY_LEFT: | ||
187 | if (vc_x > 0) { | ||
188 | vc_x -= WIDTH; | ||
189 | if (vc_x < 0) | ||
190 | vc_x = 0; | ||
191 | } else if (vc_y >= 1) { | ||
192 | beep(880); | ||
193 | vc_y--; | ||
194 | vc_x = vc->vc_cols-WIDTH; | ||
195 | } else | ||
196 | beep(220); | ||
197 | break; | ||
198 | case KEY_RIGHT: | ||
199 | if (vc_x + WIDTH < vc->vc_cols) { | ||
200 | vc_x += WIDTH; | ||
201 | } else if (vc_y + 1 < vc->vc_rows) { | ||
202 | beep(880); | ||
203 | vc_y++; | ||
204 | vc_x = 0; | ||
205 | } else | ||
206 | beep(220); | ||
207 | break; | ||
208 | case KEY_DOWN: | ||
209 | if (vc_y + 1 < vc->vc_rows) | ||
210 | vc_y++; | ||
211 | else | ||
212 | beep(220); | ||
213 | break; | ||
214 | case KEY_UP: | ||
215 | if (vc_y >= 1) | ||
216 | vc_y--; | ||
217 | else | ||
218 | beep(220); | ||
219 | break; | ||
220 | case KEY_HOME: | ||
221 | vc_follow_cursor(vc); | ||
222 | break; | ||
223 | case KEY_PAGEUP: | ||
224 | vc_x = 0; | ||
225 | vc_y = 0; | ||
226 | break; | ||
227 | case KEY_PAGEDOWN: | ||
228 | vc_x = 0; | ||
229 | vc_y = vc->vc_rows-1; | ||
230 | break; | ||
231 | default: | ||
232 | ret = NOTIFY_OK; | ||
233 | break; | ||
234 | } | ||
235 | if (ret == NOTIFY_STOP) | ||
236 | vc_refresh(vc); | ||
237 | } | ||
238 | break; | ||
239 | case KBD_POST_KEYSYM: | ||
240 | { | ||
241 | unsigned char type = KTYP(param->value) - 0xf0; | ||
242 | if (type == KT_SPEC) { | ||
243 | unsigned char val = KVAL(param->value); | ||
244 | int on_off = -1; | ||
245 | |||
246 | switch (val) { | ||
247 | case KVAL(K_CAPS): | ||
248 | on_off = vc_kbd_led(kbd_table + fg_console, | ||
249 | VC_CAPSLOCK); | ||
250 | break; | ||
251 | case KVAL(K_NUM): | ||
252 | on_off = vc_kbd_led(kbd_table + fg_console, | ||
253 | VC_NUMLOCK); | ||
254 | break; | ||
255 | case KVAL(K_HOLD): | ||
256 | on_off = vc_kbd_led(kbd_table + fg_console, | ||
257 | VC_SCROLLOCK); | ||
258 | break; | ||
259 | } | ||
260 | if (on_off == 1) | ||
261 | beep(880); | ||
262 | else if (on_off == 0) | ||
263 | beep(440); | ||
264 | } | ||
265 | } | ||
266 | case KBD_UNBOUND_KEYCODE: | ||
267 | case KBD_UNICODE: | ||
268 | case KBD_KEYSYM: | ||
269 | /* Unused */ | ||
270 | break; | ||
271 | } | ||
272 | return ret; | ||
273 | } | ||
274 | |||
275 | static struct notifier_block keyboard_notifier_block = { | ||
276 | .notifier_call = keyboard_notifier_call, | ||
277 | }; | ||
278 | |||
279 | static int vt_notifier_call(struct notifier_block *blk, | ||
280 | unsigned long code, void *_param) | ||
281 | { | ||
282 | struct vt_notifier_param *param = _param; | ||
283 | struct vc_data *vc = param->vc; | ||
284 | switch (code) { | ||
285 | case VT_ALLOCATE: | ||
286 | break; | ||
287 | case VT_DEALLOCATE: | ||
288 | break; | ||
289 | case VT_WRITE: | ||
290 | { | ||
291 | unsigned char c = param->c; | ||
292 | if (vc->vc_num != fg_console) | ||
293 | break; | ||
294 | switch (c) { | ||
295 | case '\b': | ||
296 | case 127: | ||
297 | if (console_cursor > 0) { | ||
298 | console_cursor--; | ||
299 | console_buf[console_cursor] = ' '; | ||
300 | } | ||
301 | break; | ||
302 | case '\n': | ||
303 | case '\v': | ||
304 | case '\f': | ||
305 | case '\r': | ||
306 | console_newline = 1; | ||
307 | break; | ||
308 | case '\t': | ||
309 | c = ' '; | ||
310 | /* Fallthrough */ | ||
311 | default: | ||
312 | if (c < 32) | ||
313 | /* Ignore other control sequences */ | ||
314 | break; | ||
315 | if (console_newline) { | ||
316 | memset(console_buf, 0, sizeof(console_buf)); | ||
317 | console_cursor = 0; | ||
318 | console_newline = 0; | ||
319 | } | ||
320 | if (console_cursor == WIDTH) | ||
321 | memmove(console_buf, &console_buf[1], | ||
322 | (WIDTH-1) * sizeof(*console_buf)); | ||
323 | else | ||
324 | console_cursor++; | ||
325 | console_buf[console_cursor-1] = c; | ||
326 | break; | ||
327 | } | ||
328 | if (console_show) | ||
329 | braille_write(console_buf); | ||
330 | else { | ||
331 | vc_maybe_cursor_moved(vc); | ||
332 | vc_refresh(vc); | ||
333 | } | ||
334 | break; | ||
335 | } | ||
336 | case VT_UPDATE: | ||
337 | /* Maybe a VT switch, flush */ | ||
338 | if (console_show) { | ||
339 | if (vc->vc_num != lastVC) { | ||
340 | lastVC = vc->vc_num; | ||
341 | memset(console_buf, 0, sizeof(console_buf)); | ||
342 | console_cursor = 0; | ||
343 | braille_write(console_buf); | ||
344 | } | ||
345 | } else { | ||
346 | vc_maybe_cursor_moved(vc); | ||
347 | vc_refresh(vc); | ||
348 | } | ||
349 | break; | ||
350 | } | ||
351 | return NOTIFY_OK; | ||
352 | } | ||
353 | |||
354 | static struct notifier_block vt_notifier_block = { | ||
355 | .notifier_call = vt_notifier_call, | ||
356 | }; | ||
357 | |||
358 | /* | ||
359 | * Called from printk.c when console=brl is given | ||
360 | */ | ||
361 | |||
362 | int braille_register_console(struct console *console, int index, | ||
363 | char *console_options, char *braille_options) | ||
364 | { | ||
365 | int ret; | ||
366 | if (!console_options) | ||
367 | /* Only support VisioBraille for now */ | ||
368 | console_options = "57600o8"; | ||
369 | if (braille_co) | ||
370 | return -ENODEV; | ||
371 | if (console->setup) { | ||
372 | ret = console->setup(console, console_options); | ||
373 | if (ret != 0) | ||
374 | return ret; | ||
375 | } | ||
376 | console->flags |= CON_ENABLED; | ||
377 | console->index = index; | ||
378 | braille_co = console; | ||
379 | return 0; | ||
380 | } | ||
381 | |||
382 | int braille_unregister_console(struct console *console) | ||
383 | { | ||
384 | if (braille_co != console) | ||
385 | return -EINVAL; | ||
386 | braille_co = NULL; | ||
387 | return 0; | ||
388 | } | ||
389 | |||
390 | static int __init braille_init(void) | ||
391 | { | ||
392 | register_keyboard_notifier(&keyboard_notifier_block); | ||
393 | register_vt_notifier(&vt_notifier_block); | ||
394 | return 0; | ||
395 | } | ||
396 | |||
397 | console_initcall(braille_init); | ||