xref: /linux/drivers/accessibility/braille/braille_console.c (revision 8c994eff8fcfe8ecb1f1dbebed25b4d7bb75be12)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Minimalistic braille device kernel support.
4  *
5  * By default, shows console messages on the braille device.
6  * Pressing Insert switches to VC browsing.
7  *
8  *  Copyright (C) Samuel Thibault <samuel.thibault@ens-lyon.org>
9  */
10 
11 #include <linux/kernel.h>
12 #include <linux/module.h>
13 #include <linux/moduleparam.h>
14 #include <linux/console.h>
15 #include <linux/notifier.h>
16 
17 #include <linux/selection.h>
18 #include <linux/vt_kern.h>
19 #include <linux/consolemap.h>
20 
21 #include <linux/keyboard.h>
22 #include <linux/kbd_kern.h>
23 #include <linux/input.h>
24 
25 MODULE_AUTHOR("samuel.thibault@ens-lyon.org");
26 MODULE_DESCRIPTION("braille device");
27 
28 /*
29  * Braille device support part.
30  */
31 
32 /* Emit various sounds */
33 static bool sound;
34 module_param(sound, bool, 0);
35 MODULE_PARM_DESC(sound, "emit sounds");
36 
37 static void beep(unsigned int freq)
38 {
39 	if (sound)
40 		kd_mksound(freq, HZ/10);
41 }
42 
43 /* mini console */
44 #define WIDTH 40
45 #define BRAILLE_KEY KEY_INSERT
46 static u16 console_buf[WIDTH];
47 static int console_cursor;
48 
49 /* mini view of VC */
50 static int vc_x, vc_y, lastvc_x, lastvc_y;
51 
52 /* show console ? (or show VC) */
53 static int console_show = 1;
54 /* pending newline ? */
55 static int console_newline = 1;
56 static int lastVC = -1;
57 
58 static struct console *braille_co;
59 
60 /* Very VisioBraille-specific */
61 static void braille_write(u16 *buf)
62 {
63 	static u16 lastwrite[WIDTH];
64 	unsigned char data[1 + 1 + 2*WIDTH + 2 + 1], csum = 0, *c;
65 	u16 out;
66 	int i;
67 
68 	if (!braille_co)
69 		return;
70 
71 	if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf)))
72 		return;
73 	memcpy(lastwrite, buf, WIDTH * sizeof(*buf));
74 
75 #define SOH 1
76 #define STX 2
77 #define ETX 2
78 #define EOT 4
79 #define ENQ 5
80 	data[0] = STX;
81 	data[1] = '>';
82 	csum ^= '>';
83 	c = &data[2];
84 	for (i = 0; i < WIDTH; i++) {
85 		out = buf[i];
86 		if (out >= 0x100)
87 			out = '?';
88 		else if (out == 0x00)
89 			out = ' ';
90 		csum ^= out;
91 		if (out <= 0x05) {
92 			*c++ = SOH;
93 			out |= 0x40;
94 		}
95 		*c++ = out;
96 	}
97 
98 	if (csum <= 0x05) {
99 		*c++ = SOH;
100 		csum |= 0x40;
101 	}
102 	*c++ = csum;
103 	*c++ = ETX;
104 
105 	braille_co->write(braille_co, data, c - data);
106 }
107 
108 /* Follow the VC cursor*/
109 static void vc_follow_cursor(struct vc_data *vc)
110 {
111 	vc_x = vc->state.x - (vc->state.x % WIDTH);
112 	vc_y = vc->state.y;
113 	lastvc_x = vc->state.x;
114 	lastvc_y = vc->state.y;
115 }
116 
117 /* Maybe the VC cursor moved, if so follow it */
118 static void vc_maybe_cursor_moved(struct vc_data *vc)
119 {
120 	if (vc->state.x != lastvc_x || vc->state.y != lastvc_y)
121 		vc_follow_cursor(vc);
122 }
123 
124 /* Show portion of VC at vc_x, vc_y */
125 static void vc_refresh(struct vc_data *vc)
126 {
127 	u16 buf[WIDTH];
128 	int i;
129 
130 	for (i = 0; i < WIDTH; i++) {
131 		u16 glyph = screen_glyph(vc,
132 				2 * (vc_x + i) + vc_y * vc->vc_size_row);
133 		buf[i] = inverse_translate(vc, glyph, true);
134 	}
135 	braille_write(buf);
136 }
137 
138 /*
139  * Link to keyboard
140  */
141 
142 static int keyboard_notifier_call(struct notifier_block *blk,
143 				  unsigned long code, void *_param)
144 {
145 	struct keyboard_notifier_param *param = _param;
146 	struct vc_data *vc = param->vc;
147 	int ret = NOTIFY_OK;
148 
149 	if (!param->down)
150 		return ret;
151 
152 	switch (code) {
153 	case KBD_KEYCODE:
154 		if (console_show) {
155 			if (param->value == BRAILLE_KEY) {
156 				console_show = 0;
157 				beep(880);
158 				vc_maybe_cursor_moved(vc);
159 				vc_refresh(vc);
160 				ret = NOTIFY_STOP;
161 			}
162 		} else {
163 			ret = NOTIFY_STOP;
164 			switch (param->value) {
165 			case KEY_INSERT:
166 				beep(440);
167 				console_show = 1;
168 				lastVC = -1;
169 				braille_write(console_buf);
170 				break;
171 			case KEY_LEFT:
172 				if (vc_x > 0) {
173 					vc_x -= WIDTH;
174 					if (vc_x < 0)
175 						vc_x = 0;
176 				} else if (vc_y >= 1) {
177 					beep(880);
178 					vc_y--;
179 					vc_x = vc->vc_cols-WIDTH;
180 				} else
181 					beep(220);
182 				break;
183 			case KEY_RIGHT:
184 				if (vc_x + WIDTH < vc->vc_cols) {
185 					vc_x += WIDTH;
186 				} else if (vc_y + 1 < vc->vc_rows) {
187 					beep(880);
188 					vc_y++;
189 					vc_x = 0;
190 				} else
191 					beep(220);
192 				break;
193 			case KEY_DOWN:
194 				if (vc_y + 1 < vc->vc_rows)
195 					vc_y++;
196 				else
197 					beep(220);
198 				break;
199 			case KEY_UP:
200 				if (vc_y >= 1)
201 					vc_y--;
202 				else
203 					beep(220);
204 				break;
205 			case KEY_HOME:
206 				vc_follow_cursor(vc);
207 				break;
208 			case KEY_PAGEUP:
209 				vc_x = 0;
210 				vc_y = 0;
211 				break;
212 			case KEY_PAGEDOWN:
213 				vc_x = 0;
214 				vc_y = vc->vc_rows-1;
215 				break;
216 			default:
217 				ret = NOTIFY_OK;
218 				break;
219 			}
220 			if (ret == NOTIFY_STOP)
221 				vc_refresh(vc);
222 		}
223 		break;
224 	case KBD_POST_KEYSYM:
225 	{
226 		unsigned char type = KTYP(param->value) - 0xf0;
227 
228 		if (type == KT_SPEC) {
229 			unsigned char val = KVAL(param->value);
230 			int on_off = -1;
231 
232 			switch (val) {
233 			case KVAL(K_CAPS):
234 				on_off = vt_get_leds(fg_console, VC_CAPSLOCK);
235 				break;
236 			case KVAL(K_NUM):
237 				on_off = vt_get_leds(fg_console, VC_NUMLOCK);
238 				break;
239 			case KVAL(K_HOLD):
240 				on_off = vt_get_leds(fg_console, VC_SCROLLOCK);
241 				break;
242 			}
243 			if (on_off == 1)
244 				beep(880);
245 			else if (on_off == 0)
246 				beep(440);
247 		}
248 	}
249 		break;
250 	case KBD_UNBOUND_KEYCODE:
251 	case KBD_UNICODE:
252 	case KBD_KEYSYM:
253 		/* Unused */
254 		break;
255 	}
256 	return ret;
257 }
258 
259 static struct notifier_block keyboard_notifier_block = {
260 	.notifier_call = keyboard_notifier_call,
261 };
262 
263 static int vt_notifier_call(struct notifier_block *blk,
264 			    unsigned long code, void *_param)
265 {
266 	struct vt_notifier_param *param = _param;
267 	struct vc_data *vc = param->vc;
268 
269 	switch (code) {
270 	case VT_ALLOCATE:
271 		break;
272 	case VT_DEALLOCATE:
273 		break;
274 	case VT_WRITE:
275 	{
276 		unsigned char c = param->c;
277 
278 		if (vc->vc_num != fg_console)
279 			break;
280 		switch (c) {
281 		case '\b':
282 		case 127:
283 			if (console_cursor > 0) {
284 				console_cursor--;
285 				console_buf[console_cursor] = ' ';
286 			}
287 			break;
288 		case '\n':
289 		case '\v':
290 		case '\f':
291 		case '\r':
292 			console_newline = 1;
293 			break;
294 		case '\t':
295 			c = ' ';
296 			fallthrough;
297 		default:
298 			if (c < 32)
299 				/* Ignore other control sequences */
300 				break;
301 			if (console_newline) {
302 				memset(console_buf, 0, sizeof(console_buf));
303 				console_cursor = 0;
304 				console_newline = 0;
305 			}
306 			if (console_cursor == WIDTH)
307 				memmove(console_buf, &console_buf[1],
308 					(WIDTH-1) * sizeof(*console_buf));
309 			else
310 				console_cursor++;
311 			console_buf[console_cursor-1] = c;
312 			break;
313 		}
314 		if (console_show)
315 			braille_write(console_buf);
316 		else {
317 			vc_maybe_cursor_moved(vc);
318 			vc_refresh(vc);
319 		}
320 		break;
321 	}
322 	case VT_UPDATE:
323 		/* Maybe a VT switch, flush */
324 		if (console_show) {
325 			if (vc->vc_num != lastVC) {
326 				lastVC = vc->vc_num;
327 				memset(console_buf, 0, sizeof(console_buf));
328 				console_cursor = 0;
329 				braille_write(console_buf);
330 			}
331 		} else {
332 			vc_maybe_cursor_moved(vc);
333 			vc_refresh(vc);
334 		}
335 		break;
336 	}
337 	return NOTIFY_OK;
338 }
339 
340 static struct notifier_block vt_notifier_block = {
341 	.notifier_call = vt_notifier_call,
342 };
343 
344 /*
345  * Called from printk.c when console=brl is given
346  */
347 
348 int braille_register_console(struct console *console, int index,
349 		char *console_options, char *braille_options)
350 {
351 	int ret;
352 
353 	if (!console_options)
354 		/* Only support VisioBraille for now */
355 		console_options = "57600o8";
356 	if (braille_co)
357 		return -ENODEV;
358 	if (console->setup) {
359 		ret = console->setup(console, console_options);
360 		if (ret != 0)
361 			return ret;
362 	}
363 	console->flags |= CON_ENABLED;
364 	console->index = index;
365 	braille_co = console;
366 	register_keyboard_notifier(&keyboard_notifier_block);
367 	register_vt_notifier(&vt_notifier_block);
368 	return 1;
369 }
370 
371 int braille_unregister_console(struct console *console)
372 {
373 	if (braille_co != console)
374 		return -EINVAL;
375 	unregister_keyboard_notifier(&keyboard_notifier_block);
376 	unregister_vt_notifier(&vt_notifier_block);
377 	braille_co = NULL;
378 	return 1;
379 }
380