forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
380 lines
7.3 KiB
380 lines
7.3 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* Minimalistic braille device kernel support. |
|
* |
|
* By default, shows console messages on the braille device. |
|
* Pressing Insert switches to VC browsing. |
|
* |
|
* Copyright (C) Samuel Thibault <[email protected]> |
|
*/ |
|
|
|
#include <linux/kernel.h> |
|
#include <linux/module.h> |
|
#include <linux/moduleparam.h> |
|
#include <linux/console.h> |
|
#include <linux/notifier.h> |
|
|
|
#include <linux/selection.h> |
|
#include <linux/vt_kern.h> |
|
#include <linux/consolemap.h> |
|
|
|
#include <linux/keyboard.h> |
|
#include <linux/kbd_kern.h> |
|
#include <linux/input.h> |
|
|
|
MODULE_AUTHOR("[email protected]"); |
|
MODULE_DESCRIPTION("braille device"); |
|
MODULE_LICENSE("GPL"); |
|
|
|
/* |
|
* Braille device support part. |
|
*/ |
|
|
|
/* Emit various sounds */ |
|
static bool sound; |
|
module_param(sound, bool, 0); |
|
MODULE_PARM_DESC(sound, "emit sounds"); |
|
|
|
static void beep(unsigned int freq) |
|
{ |
|
if (sound) |
|
kd_mksound(freq, HZ/10); |
|
} |
|
|
|
/* mini console */ |
|
#define WIDTH 40 |
|
#define BRAILLE_KEY KEY_INSERT |
|
static u16 console_buf[WIDTH]; |
|
static int console_cursor; |
|
|
|
/* mini view of VC */ |
|
static int vc_x, vc_y, lastvc_x, lastvc_y; |
|
|
|
/* show console ? (or show VC) */ |
|
static int console_show = 1; |
|
/* pending newline ? */ |
|
static int console_newline = 1; |
|
static int lastVC = -1; |
|
|
|
static struct console *braille_co; |
|
|
|
/* Very VisioBraille-specific */ |
|
static void braille_write(u16 *buf) |
|
{ |
|
static u16 lastwrite[WIDTH]; |
|
unsigned char data[1 + 1 + 2*WIDTH + 2 + 1], csum = 0, *c; |
|
u16 out; |
|
int i; |
|
|
|
if (!braille_co) |
|
return; |
|
|
|
if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf))) |
|
return; |
|
memcpy(lastwrite, buf, WIDTH * sizeof(*buf)); |
|
|
|
#define SOH 1 |
|
#define STX 2 |
|
#define ETX 2 |
|
#define EOT 4 |
|
#define ENQ 5 |
|
data[0] = STX; |
|
data[1] = '>'; |
|
csum ^= '>'; |
|
c = &data[2]; |
|
for (i = 0; i < WIDTH; i++) { |
|
out = buf[i]; |
|
if (out >= 0x100) |
|
out = '?'; |
|
else if (out == 0x00) |
|
out = ' '; |
|
csum ^= out; |
|
if (out <= 0x05) { |
|
*c++ = SOH; |
|
out |= 0x40; |
|
} |
|
*c++ = out; |
|
} |
|
|
|
if (csum <= 0x05) { |
|
*c++ = SOH; |
|
csum |= 0x40; |
|
} |
|
*c++ = csum; |
|
*c++ = ETX; |
|
|
|
braille_co->write(braille_co, data, c - data); |
|
} |
|
|
|
/* Follow the VC cursor*/ |
|
static void vc_follow_cursor(struct vc_data *vc) |
|
{ |
|
vc_x = vc->state.x - (vc->state.x % WIDTH); |
|
vc_y = vc->state.y; |
|
lastvc_x = vc->state.x; |
|
lastvc_y = vc->state.y; |
|
} |
|
|
|
/* Maybe the VC cursor moved, if so follow it */ |
|
static void vc_maybe_cursor_moved(struct vc_data *vc) |
|
{ |
|
if (vc->state.x != lastvc_x || vc->state.y != lastvc_y) |
|
vc_follow_cursor(vc); |
|
} |
|
|
|
/* Show portion of VC at vc_x, vc_y */ |
|
static void vc_refresh(struct vc_data *vc) |
|
{ |
|
u16 buf[WIDTH]; |
|
int i; |
|
|
|
for (i = 0; i < WIDTH; i++) { |
|
u16 glyph = screen_glyph(vc, |
|
2 * (vc_x + i) + vc_y * vc->vc_size_row); |
|
buf[i] = inverse_translate(vc, glyph, 1); |
|
} |
|
braille_write(buf); |
|
} |
|
|
|
/* |
|
* Link to keyboard |
|
*/ |
|
|
|
static int keyboard_notifier_call(struct notifier_block *blk, |
|
unsigned long code, void *_param) |
|
{ |
|
struct keyboard_notifier_param *param = _param; |
|
struct vc_data *vc = param->vc; |
|
int ret = NOTIFY_OK; |
|
|
|
if (!param->down) |
|
return ret; |
|
|
|
switch (code) { |
|
case KBD_KEYCODE: |
|
if (console_show) { |
|
if (param->value == BRAILLE_KEY) { |
|
console_show = 0; |
|
beep(880); |
|
vc_maybe_cursor_moved(vc); |
|
vc_refresh(vc); |
|
ret = NOTIFY_STOP; |
|
} |
|
} else { |
|
ret = NOTIFY_STOP; |
|
switch (param->value) { |
|
case KEY_INSERT: |
|
beep(440); |
|
console_show = 1; |
|
lastVC = -1; |
|
braille_write(console_buf); |
|
break; |
|
case KEY_LEFT: |
|
if (vc_x > 0) { |
|
vc_x -= WIDTH; |
|
if (vc_x < 0) |
|
vc_x = 0; |
|
} else if (vc_y >= 1) { |
|
beep(880); |
|
vc_y--; |
|
vc_x = vc->vc_cols-WIDTH; |
|
} else |
|
beep(220); |
|
break; |
|
case KEY_RIGHT: |
|
if (vc_x + WIDTH < vc->vc_cols) { |
|
vc_x += WIDTH; |
|
} else if (vc_y + 1 < vc->vc_rows) { |
|
beep(880); |
|
vc_y++; |
|
vc_x = 0; |
|
} else |
|
beep(220); |
|
break; |
|
case KEY_DOWN: |
|
if (vc_y + 1 < vc->vc_rows) |
|
vc_y++; |
|
else |
|
beep(220); |
|
break; |
|
case KEY_UP: |
|
if (vc_y >= 1) |
|
vc_y--; |
|
else |
|
beep(220); |
|
break; |
|
case KEY_HOME: |
|
vc_follow_cursor(vc); |
|
break; |
|
case KEY_PAGEUP: |
|
vc_x = 0; |
|
vc_y = 0; |
|
break; |
|
case KEY_PAGEDOWN: |
|
vc_x = 0; |
|
vc_y = vc->vc_rows-1; |
|
break; |
|
default: |
|
ret = NOTIFY_OK; |
|
break; |
|
} |
|
if (ret == NOTIFY_STOP) |
|
vc_refresh(vc); |
|
} |
|
break; |
|
case KBD_POST_KEYSYM: |
|
{ |
|
unsigned char type = KTYP(param->value) - 0xf0; |
|
|
|
if (type == KT_SPEC) { |
|
unsigned char val = KVAL(param->value); |
|
int on_off = -1; |
|
|
|
switch (val) { |
|
case KVAL(K_CAPS): |
|
on_off = vt_get_leds(fg_console, VC_CAPSLOCK); |
|
break; |
|
case KVAL(K_NUM): |
|
on_off = vt_get_leds(fg_console, VC_NUMLOCK); |
|
break; |
|
case KVAL(K_HOLD): |
|
on_off = vt_get_leds(fg_console, VC_SCROLLOCK); |
|
break; |
|
} |
|
if (on_off == 1) |
|
beep(880); |
|
else if (on_off == 0) |
|
beep(440); |
|
} |
|
} |
|
break; |
|
case KBD_UNBOUND_KEYCODE: |
|
case KBD_UNICODE: |
|
case KBD_KEYSYM: |
|
/* Unused */ |
|
break; |
|
} |
|
return ret; |
|
} |
|
|
|
static struct notifier_block keyboard_notifier_block = { |
|
.notifier_call = keyboard_notifier_call, |
|
}; |
|
|
|
static int vt_notifier_call(struct notifier_block *blk, |
|
unsigned long code, void *_param) |
|
{ |
|
struct vt_notifier_param *param = _param; |
|
struct vc_data *vc = param->vc; |
|
|
|
switch (code) { |
|
case VT_ALLOCATE: |
|
break; |
|
case VT_DEALLOCATE: |
|
break; |
|
case VT_WRITE: |
|
{ |
|
unsigned char c = param->c; |
|
|
|
if (vc->vc_num != fg_console) |
|
break; |
|
switch (c) { |
|
case '\b': |
|
case 127: |
|
if (console_cursor > 0) { |
|
console_cursor--; |
|
console_buf[console_cursor] = ' '; |
|
} |
|
break; |
|
case '\n': |
|
case '\v': |
|
case '\f': |
|
case '\r': |
|
console_newline = 1; |
|
break; |
|
case '\t': |
|
c = ' '; |
|
fallthrough; |
|
default: |
|
if (c < 32) |
|
/* Ignore other control sequences */ |
|
break; |
|
if (console_newline) { |
|
memset(console_buf, 0, sizeof(console_buf)); |
|
console_cursor = 0; |
|
console_newline = 0; |
|
} |
|
if (console_cursor == WIDTH) |
|
memmove(console_buf, &console_buf[1], |
|
(WIDTH-1) * sizeof(*console_buf)); |
|
else |
|
console_cursor++; |
|
console_buf[console_cursor-1] = c; |
|
break; |
|
} |
|
if (console_show) |
|
braille_write(console_buf); |
|
else { |
|
vc_maybe_cursor_moved(vc); |
|
vc_refresh(vc); |
|
} |
|
break; |
|
} |
|
case VT_UPDATE: |
|
/* Maybe a VT switch, flush */ |
|
if (console_show) { |
|
if (vc->vc_num != lastVC) { |
|
lastVC = vc->vc_num; |
|
memset(console_buf, 0, sizeof(console_buf)); |
|
console_cursor = 0; |
|
braille_write(console_buf); |
|
} |
|
} else { |
|
vc_maybe_cursor_moved(vc); |
|
vc_refresh(vc); |
|
} |
|
break; |
|
} |
|
return NOTIFY_OK; |
|
} |
|
|
|
static struct notifier_block vt_notifier_block = { |
|
.notifier_call = vt_notifier_call, |
|
}; |
|
|
|
/* |
|
* Called from printk.c when console=brl is given |
|
*/ |
|
|
|
int braille_register_console(struct console *console, int index, |
|
char *console_options, char *braille_options) |
|
{ |
|
int ret; |
|
|
|
if (!console_options) |
|
/* Only support VisioBraille for now */ |
|
console_options = "57600o8"; |
|
if (braille_co) |
|
return -ENODEV; |
|
if (console->setup) { |
|
ret = console->setup(console, console_options); |
|
if (ret != 0) |
|
return ret; |
|
} |
|
console->flags |= CON_ENABLED; |
|
console->index = index; |
|
braille_co = console; |
|
register_keyboard_notifier(&keyboard_notifier_block); |
|
register_vt_notifier(&vt_notifier_block); |
|
return 1; |
|
} |
|
|
|
int braille_unregister_console(struct console *console) |
|
{ |
|
if (braille_co != console) |
|
return -EINVAL; |
|
unregister_keyboard_notifier(&keyboard_notifier_block); |
|
unregister_vt_notifier(&vt_notifier_block); |
|
braille_co = NULL; |
|
return 1; |
|
}
|
|
|