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