diff options
Diffstat (limited to 'drivers/accessibility/speakup/main.c')
-rw-r--r-- | drivers/accessibility/speakup/main.c | 2460 |
1 files changed, 2460 insertions, 0 deletions
diff --git a/drivers/accessibility/speakup/main.c b/drivers/accessibility/speakup/main.c new file mode 100644 index 000000000000..ddfd12afe3b9 --- /dev/null +++ b/drivers/accessibility/speakup/main.c @@ -0,0 +1,2460 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* speakup.c + * review functions for the speakup screen review package. + * originally written by: Kirk Reiser and Andy Berdan. + * + * extensively modified by David Borowski. + * + ** Copyright (C) 1998 Kirk Reiser. + * Copyright (C) 2003 David Borowski. + */ + +#include <linux/kernel.h> +#include <linux/vt.h> +#include <linux/tty.h> +#include <linux/mm.h> /* __get_free_page() and friends */ +#include <linux/vt_kern.h> +#include <linux/ctype.h> +#include <linux/selection.h> +#include <linux/unistd.h> +#include <linux/jiffies.h> +#include <linux/kthread.h> +#include <linux/keyboard.h> /* for KT_SHIFT */ +#include <linux/kbd_kern.h> /* for vc_kbd_* and friends */ +#include <linux/input.h> +#include <linux/kmod.h> + +/* speakup_*_selection */ +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/consolemap.h> + +#include <linux/spinlock.h> +#include <linux/notifier.h> + +#include <linux/uaccess.h> /* copy_from|to|user() and others */ + +#include "spk_priv.h" +#include "speakup.h" + +#define MAX_DELAY msecs_to_jiffies(500) +#define MINECHOCHAR SPACE + +MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>"); +MODULE_AUTHOR("Daniel Drake <dsd@gentoo.org>"); +MODULE_DESCRIPTION("Speakup console speech"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(SPEAKUP_VERSION); + +char *synth_name; +module_param_named(synth, synth_name, charp, 0444); +module_param_named(quiet, spk_quiet_boot, bool, 0444); + +MODULE_PARM_DESC(synth, "Synth to start if speakup is built in."); +MODULE_PARM_DESC(quiet, "Do not announce when the synthesizer is found."); + +special_func spk_special_handler; + +short spk_pitch_shift, synth_flags; +static u16 buf[256]; +int spk_attrib_bleep, spk_bleeps, spk_bleep_time = 10; +int spk_no_intr, spk_spell_delay; +int spk_key_echo, spk_say_word_ctl; +int spk_say_ctrl, spk_bell_pos; +short spk_punc_mask; +int spk_punc_level, spk_reading_punc; +char spk_str_caps_start[MAXVARLEN + 1] = "\0"; +char spk_str_caps_stop[MAXVARLEN + 1] = "\0"; +char spk_str_pause[MAXVARLEN + 1] = "\0"; +bool spk_paused; +const struct st_bits_data spk_punc_info[] = { + {"none", "", 0}, + {"some", "/$%&@", SOME}, + {"most", "$%&#()=+*/@^<>|\\", MOST}, + {"all", "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", PUNC}, + {"delimiters", "", B_WDLM}, + {"repeats", "()", CH_RPT}, + {"extended numeric", "", B_EXNUM}, + {"symbols", "", B_SYM}, + {NULL, NULL} +}; + +static char mark_cut_flag; +#define MAX_KEY 160 +static u_char *spk_shift_table; +u_char *spk_our_keys[MAX_KEY]; +u_char spk_key_buf[600]; +const u_char spk_key_defaults[] = { +#include "speakupmap.h" +}; + +/* Speakup Cursor Track Variables */ +static int cursor_track = 1, prev_cursor_track = 1; + +/* cursor track modes, must be ordered same as cursor_msgs */ +enum { + CT_Off = 0, + CT_On, + CT_Highlight, + CT_Window, + CT_Max +}; + +#define read_all_mode CT_Max + +static struct tty_struct *tty; + +static void spkup_write(const u16 *in_buf, int count); + +static char *phonetic[] = { + "alfa", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel", + "india", "juliett", "keelo", "leema", "mike", "november", "oscar", + "papa", + "keh beck", "romeo", "sierra", "tango", "uniform", "victer", "whiskey", + "x ray", "yankee", "zulu" +}; + +/* array of 256 char pointers (one for each character description) + * initialized to default_chars and user selectable via + * /proc/speakup/characters + */ +char *spk_characters[256]; + +char *spk_default_chars[256] = { +/*000*/ "null", "^a", "^b", "^c", "^d", "^e", "^f", "^g", +/*008*/ "^h", "^i", "^j", "^k", "^l", "^m", "^n", "^o", +/*016*/ "^p", "^q", "^r", "^s", "^t", "^u", "^v", "^w", +/*024*/ "^x", "^y", "^z", "control", "control", "control", "control", + "control", +/*032*/ "space", "bang!", "quote", "number", "dollar", "percent", "and", + "tick", +/*040*/ "left paren", "right paren", "star", "plus", "comma", "dash", + "dot", + "slash", +/*048*/ "zero", "one", "two", "three", "four", "five", "six", "seven", + "eight", "nine", +/*058*/ "colon", "semmy", "less", "equals", "greater", "question", "at", +/*065*/ "EIGH", "B", "C", "D", "E", "F", "G", +/*072*/ "H", "I", "J", "K", "L", "M", "N", "O", +/*080*/ "P", "Q", "R", "S", "T", "U", "V", "W", "X", +/*089*/ "Y", "ZED", "left bracket", "backslash", "right bracket", + "caret", + "line", +/*096*/ "accent", "a", "b", "c", "d", "e", "f", "g", +/*104*/ "h", "i", "j", "k", "l", "m", "n", "o", +/*112*/ "p", "q", "r", "s", "t", "u", "v", "w", +/*120*/ "x", "y", "zed", "left brace", "bar", "right brace", "tihlduh", +/*127*/ "del", "control", "control", "control", "control", "control", + "control", "control", "control", "control", "control", +/*138*/ "control", "control", "control", "control", "control", + "control", "control", "control", "control", "control", + "control", "control", +/*150*/ "control", "control", "control", "control", "control", + "control", "control", "control", "control", "control", +/*160*/ "nbsp", "inverted bang", +/*162*/ "cents", "pounds", "currency", "yen", "broken bar", "section", +/*168*/ "diaeresis", "copyright", "female ordinal", "double left angle", +/*172*/ "not", "soft hyphen", "registered", "macron", +/*176*/ "degrees", "plus or minus", "super two", "super three", +/*180*/ "acute accent", "micro", "pilcrow", "middle dot", +/*184*/ "cedilla", "super one", "male ordinal", "double right angle", +/*188*/ "one quarter", "one half", "three quarters", + "inverted question", +/*192*/ "A GRAVE", "A ACUTE", "A CIRCUMFLEX", "A TILDE", "A OOMLAUT", + "A RING", +/*198*/ "AE", "C CIDELLA", "E GRAVE", "E ACUTE", "E CIRCUMFLEX", + "E OOMLAUT", +/*204*/ "I GRAVE", "I ACUTE", "I CIRCUMFLEX", "I OOMLAUT", "ETH", + "N TILDE", +/*210*/ "O GRAVE", "O ACUTE", "O CIRCUMFLEX", "O TILDE", "O OOMLAUT", +/*215*/ "multiplied by", "O STROKE", "U GRAVE", "U ACUTE", + "U CIRCUMFLEX", +/*220*/ "U OOMLAUT", "Y ACUTE", "THORN", "sharp s", "a grave", +/*225*/ "a acute", "a circumflex", "a tilde", "a oomlaut", "a ring", +/*230*/ "ae", "c cidella", "e grave", "e acute", +/*234*/ "e circumflex", "e oomlaut", "i grave", "i acute", + "i circumflex", +/*239*/ "i oomlaut", "eth", "n tilde", "o grave", "o acute", + "o circumflex", +/*245*/ "o tilde", "o oomlaut", "divided by", "o stroke", "u grave", + "u acute", +/* 251 */ "u circumflex", "u oomlaut", "y acute", "thorn", "y oomlaut" +}; + +/* array of 256 u_short (one for each character) + * initialized to default_chartab and user selectable via + * /sys/module/speakup/parameters/chartab + */ +u_short spk_chartab[256]; + +static u_short default_chartab[256] = { + B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, /* 0-7 */ + B_CTL, B_CTL, A_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, /* 8-15 */ + B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, /*16-23 */ + B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, /* 24-31 */ + WDLM, A_PUNC, PUNC, PUNC, PUNC, PUNC, PUNC, A_PUNC, /* !"#$%&' */ + PUNC, PUNC, PUNC, PUNC, A_PUNC, A_PUNC, A_PUNC, PUNC, /* ()*+, -./ */ + NUM, NUM, NUM, NUM, NUM, NUM, NUM, NUM, /* 01234567 */ + NUM, NUM, A_PUNC, PUNC, PUNC, PUNC, PUNC, A_PUNC, /* 89:;<=>? */ + PUNC, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, /* @ABCDEFG */ + A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, /* HIJKLMNO */ + A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, /* PQRSTUVW */ + A_CAP, A_CAP, A_CAP, PUNC, PUNC, PUNC, PUNC, PUNC, /* XYZ[\]^_ */ + PUNC, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, /* `abcdefg */ + ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, /* hijklmno */ + ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, /* pqrstuvw */ + ALPHA, ALPHA, ALPHA, PUNC, PUNC, PUNC, PUNC, 0, /* xyz{|}~ */ + B_CAPSYM, B_CAPSYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, /* 128-134 */ + B_SYM, /* 135 */ + B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, /* 136-142 */ + B_CAPSYM, /* 143 */ + B_CAPSYM, B_CAPSYM, B_SYM, B_CAPSYM, B_SYM, B_SYM, B_SYM, /* 144-150 */ + B_SYM, /* 151 */ + B_SYM, B_SYM, B_CAPSYM, B_CAPSYM, B_SYM, B_SYM, B_SYM, /*152-158 */ + B_SYM, /* 159 */ + WDLM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_CAPSYM, /* 160-166 */ + B_SYM, /* 167 */ + B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, /* 168-175 */ + B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, /* 176-183 */ + B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, /* 184-191 */ + A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, /* 192-199 */ + A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, /* 200-207 */ + A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, B_SYM, /* 208-215 */ + A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, ALPHA, /* 216-223 */ + ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, /* 224-231 */ + ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, /* 232-239 */ + ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, B_SYM, /* 240-247 */ + ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA /* 248-255 */ +}; + +struct task_struct *speakup_task; +struct bleep spk_unprocessed_sound; +static int spk_keydown; +static u16 spk_lastkey; +static u_char spk_close_press, keymap_flags; +static u_char last_keycode, this_speakup_key; +static u_long last_spk_jiffy; + +struct st_spk_t *speakup_console[MAX_NR_CONSOLES]; + +DEFINE_MUTEX(spk_mutex); + +static int keyboard_notifier_call(struct notifier_block *, + unsigned long code, void *param); + +static struct notifier_block keyboard_notifier_block = { + .notifier_call = keyboard_notifier_call, +}; + +static int vt_notifier_call(struct notifier_block *, + unsigned long code, void *param); + +static struct notifier_block vt_notifier_block = { + .notifier_call = vt_notifier_call, +}; + +static unsigned char get_attributes(struct vc_data *vc, u16 *pos) +{ + pos = screen_pos(vc, pos - (u16 *)vc->vc_origin, 1); + return (scr_readw(pos) & ~vc->vc_hi_font_mask) >> 8; +} + +static void speakup_date(struct vc_data *vc) +{ + spk_x = spk_cx = vc->state.x; + spk_y = spk_cy = vc->state.y; + spk_pos = spk_cp = vc->vc_pos; + spk_old_attr = spk_attr; + spk_attr = get_attributes(vc, (u_short *)spk_pos); +} + +static void bleep(u_short val) +{ + static const short vals[] = { + 350, 370, 392, 414, 440, 466, 491, 523, 554, 587, 619, 659 + }; + short freq; + int time = spk_bleep_time; + + freq = vals[val % 12]; + if (val > 11) + freq *= (1 << (val / 12)); + spk_unprocessed_sound.freq = freq; + spk_unprocessed_sound.jiffies = msecs_to_jiffies(time); + spk_unprocessed_sound.active = 1; + /* We can only have 1 active sound at a time. */ +} + +static void speakup_shut_up(struct vc_data *vc) +{ + if (spk_killed) + return; + spk_shut_up |= 0x01; + spk_parked &= 0xfe; + speakup_date(vc); + if (synth) + spk_do_flush(); +} + +static void speech_kill(struct vc_data *vc) +{ + char val = synth->is_alive(synth); + + if (val == 0) + return; + + /* re-enables synth, if disabled */ + if (val == 2 || spk_killed) { + /* dead */ + spk_shut_up &= ~0x40; + synth_printf("%s\n", spk_msg_get(MSG_IAM_ALIVE)); + } else { + synth_printf("%s\n", spk_msg_get(MSG_YOU_KILLED_SPEAKUP)); + spk_shut_up |= 0x40; + } +} + +static void speakup_off(struct vc_data *vc) +{ + if (spk_shut_up & 0x80) { + spk_shut_up &= 0x7f; + synth_printf("%s\n", spk_msg_get(MSG_HEY_THATS_BETTER)); + } else { + spk_shut_up |= 0x80; + synth_printf("%s\n", spk_msg_get(MSG_YOU_TURNED_ME_OFF)); + } + speakup_date(vc); +} + +static void speakup_parked(struct vc_data *vc) +{ + if (spk_parked & 0x80) { + spk_parked = 0; + synth_printf("%s\n", spk_msg_get(MSG_UNPARKED)); + } else { + spk_parked |= 0x80; + synth_printf("%s\n", spk_msg_get(MSG_PARKED)); + } +} + +static void speakup_cut(struct vc_data *vc) +{ + static const char err_buf[] = "set selection failed"; + int ret; + + if (!mark_cut_flag) { + mark_cut_flag = 1; + spk_xs = (u_short)spk_x; + spk_ys = (u_short)spk_y; + spk_sel_cons = vc; + synth_printf("%s\n", spk_msg_get(MSG_MARK)); + return; + } + spk_xe = (u_short)spk_x; + spk_ye = (u_short)spk_y; + mark_cut_flag = 0; + synth_printf("%s\n", spk_msg_get(MSG_CUT)); + + speakup_clear_selection(); + ret = speakup_set_selection(tty); + + switch (ret) { + case 0: + break; /* no error */ + case -EFAULT: + pr_warn("%sEFAULT\n", err_buf); + break; + case -EINVAL: + pr_warn("%sEINVAL\n", err_buf); + break; + case -ENOMEM: + pr_warn("%sENOMEM\n", err_buf); + break; + } +} + +static void speakup_paste(struct vc_data *vc) +{ + if (mark_cut_flag) { + mark_cut_flag = 0; + synth_printf("%s\n", spk_msg_get(MSG_MARK_CLEARED)); + } else { + synth_printf("%s\n", spk_msg_get(MSG_PASTE)); + speakup_paste_selection(tty); + } +} + +static void say_attributes(struct vc_data *vc) +{ + int fg = spk_attr & 0x0f; + int bg = spk_attr >> 4; + + if (fg > 8) { + synth_printf("%s ", spk_msg_get(MSG_BRIGHT)); + fg -= 8; + } + synth_printf("%s", spk_msg_get(MSG_COLORS_START + fg)); + if (bg > 7) { + synth_printf(" %s ", spk_msg_get(MSG_ON_BLINKING)); + bg -= 8; + } else { + synth_printf(" %s ", spk_msg_get(MSG_ON)); + } + synth_printf("%s\n", spk_msg_get(MSG_COLORS_START + bg)); +} + +enum { + edge_top = 1, + edge_bottom, + edge_left, + edge_right, + edge_quiet +}; + +static void announce_edge(struct vc_data *vc, int msg_id) +{ + if (spk_bleeps & 1) + bleep(spk_y); + if ((spk_bleeps & 2) && (msg_id < edge_quiet)) + synth_printf("%s\n", + spk_msg_get(MSG_EDGE_MSGS_START + msg_id - 1)); +} + +static void speak_char(u16 ch) +{ + char *cp; + struct var_t *direct = spk_get_var(DIRECT); + + if (ch >= 0x100 || (direct && direct->u.n.value)) { + if (ch < 0x100 && IS_CHAR(ch, B_CAP)) { + spk_pitch_shift++; + synth_printf("%s", spk_str_caps_start); + } + synth_putwc_s(ch); + if (ch < 0x100 && IS_CHAR(ch, B_CAP)) + synth_printf("%s", spk_str_caps_stop); + return; + } + + cp = spk_characters[ch]; + if (!cp) { + pr_info("%s: cp == NULL!\n", __func__); + return; + } + if (IS_CHAR(ch, B_CAP)) { + spk_pitch_shift++; + synth_printf("%s %s %s", + spk_str_caps_start, cp, spk_str_caps_stop); + } else { + if (*cp == '^') { + cp++; + synth_printf(" %s%s ", spk_msg_get(MSG_CTRL), cp); + } else { + synth_printf(" %s ", cp); + } + } +} + +static u16 get_char(struct vc_data *vc, u16 *pos, u_char *attribs) +{ + u16 ch = ' '; + + if (vc && pos) { + u16 w; + u16 c; + + pos = screen_pos(vc, pos - (u16 *)vc->vc_origin, 1); + w = scr_readw(pos); + c = w & 0xff; + + if (w & vc->vc_hi_font_mask) { + w &= ~vc->vc_hi_font_mask; + c |= 0x100; + } + + ch = inverse_translate(vc, c, 1); + *attribs = (w & 0xff00) >> 8; + } + return ch; +} + +static void say_char(struct vc_data *vc) +{ + u16 ch; + + spk_old_attr = spk_attr; + ch = get_char(vc, (u_short *)spk_pos, &spk_attr); + if (spk_attr != spk_old_attr) { + if (spk_attrib_bleep & 1) + bleep(spk_y); + if (spk_attrib_bleep & 2) + say_attributes(vc); + } + speak_char(ch); +} + +static void say_phonetic_char(struct vc_data *vc) +{ + u16 ch; + + spk_old_attr = spk_attr; + ch = get_char(vc, (u_short *)spk_pos, &spk_attr); + if (ch <= 0x7f && isalpha(ch)) { + ch &= 0x1f; + synth_printf("%s\n", phonetic[--ch]); + } else { + if (ch < 0x100 && IS_CHAR(ch, B_NUM)) + synth_printf("%s ", spk_msg_get(MSG_NUMBER)); + speak_char(ch); + } +} + +static void say_prev_char(struct vc_data *vc) +{ + spk_parked |= 0x01; + if (spk_x == 0) { + announce_edge(vc, edge_left); + return; + } + spk_x--; + spk_pos -= 2; + say_char(vc); +} + +static void say_next_char(struct vc_data *vc) +{ + spk_parked |= 0x01; + if (spk_x == vc->vc_cols - 1) { + announce_edge(vc, edge_right); + return; + } + spk_x++; + spk_pos += 2; + say_char(vc); +} + +/* get_word - will first check to see if the character under the + * reading cursor is a space and if spk_say_word_ctl is true it will + * return the word space. If spk_say_word_ctl is not set it will check to + * see if there is a word starting on the next position to the right + * and return that word if it exists. If it does not exist it will + * move left to the beginning of any previous word on the line or the + * beginning off the line whichever comes first.. + */ + +static u_long get_word(struct vc_data *vc) +{ + u_long cnt = 0, tmpx = spk_x, tmp_pos = spk_pos; + u16 ch; + u16 attr_ch; + u_char temp; + + spk_old_attr = spk_attr; + ch = get_char(vc, (u_short *)tmp_pos, &temp); + +/* decided to take out the sayword if on a space (mis-information */ + if (spk_say_word_ctl && ch == SPACE) { + *buf = '\0'; + synth_printf("%s\n", spk_msg_get(MSG_SPACE)); + return 0; + } else if (tmpx < vc->vc_cols - 2 && + (ch == SPACE || ch == 0 || (ch < 0x100 && IS_WDLM(ch))) && + get_char(vc, (u_short *)tmp_pos + 1, &temp) > SPACE) { + tmp_pos += 2; + tmpx++; + } else { + while (tmpx > 0) { + ch = get_char(vc, (u_short *)tmp_pos - 1, &temp); + if ((ch == SPACE || ch == 0 || + (ch < 0x100 && IS_WDLM(ch))) && + get_char(vc, (u_short *)tmp_pos, &temp) > SPACE) + break; + tmp_pos -= 2; + tmpx--; + } + } + attr_ch = get_char(vc, (u_short *)tmp_pos, &spk_attr); + buf[cnt++] = attr_ch; + while (tmpx < vc->vc_cols - 1) { + tmp_pos += 2; + tmpx++; + ch = get_char(vc, (u_short *)tmp_pos, &temp); + if (ch == SPACE || ch == 0 || + (buf[cnt - 1] < 0x100 && IS_WDLM(buf[cnt - 1]) && + ch > SPACE)) + break; + buf[cnt++] = ch; + } + buf[cnt] = '\0'; + return cnt; +} + +static void say_word(struct vc_data *vc) +{ + u_long cnt = get_word(vc); + u_short saved_punc_mask = spk_punc_mask; + + if (cnt == 0) + return; + spk_punc_mask = PUNC; + buf[cnt++] = SPACE; + spkup_write(buf, cnt); + spk_punc_mask = saved_punc_mask; +} + +static void say_prev_word(struct vc_data *vc) +{ + u_char temp; + u16 ch; + u_short edge_said = 0, last_state = 0, state = 0; + + spk_parked |= 0x01; + + if (spk_x == 0) { + if (spk_y == 0) { + announce_edge(vc, edge_top); + return; + } + spk_y--; + spk_x = vc->vc_cols; + edge_said = edge_quiet; + } + while (1) { + if (spk_x == 0) { + if (spk_y == 0) { + edge_said = edge_top; + break; + } + if (edge_said != edge_quiet) + edge_said = edge_left; + if (state > 0) + break; + spk_y--; + spk_x = vc->vc_cols - 1; + } else { + spk_x--; + } + spk_pos -= 2; + ch = get_char(vc, (u_short *)spk_pos, &temp); + if (ch == SPACE || ch == 0) + state = 0; + else if (ch < 0x100 && IS_WDLM(ch)) + state = 1; + else + state = 2; + if (state < last_state) { + spk_pos += 2; + spk_x++; + break; + } + last_state = state; + } + if (spk_x == 0 && edge_said == edge_quiet) + edge_said = edge_left; + if (edge_said > 0 && edge_said < edge_quiet) + announce_edge(vc, edge_said); + say_word(vc); +} + +static void say_next_word(struct vc_data *vc) +{ + u_char temp; + u16 ch; + u_short edge_said = 0, last_state = 2, state = 0; + + spk_parked |= 0x01; + if (spk_x == vc->vc_cols - 1 && spk_y == vc->vc_rows - 1) { + announce_edge(vc, edge_bottom); + return; + } + while (1) { + ch = get_char(vc, (u_short *)spk_pos, &temp); + if (ch == SPACE || ch == 0) + state = 0; + else if (ch < 0x100 && IS_WDLM(ch)) + state = 1; + else + state = 2; + if (state > last_state) + break; + if (spk_x >= vc->vc_cols - 1) { + if (spk_y == vc->vc_rows - 1) { + edge_said = edge_bottom; + break; + } + state = 0; + spk_y++; + spk_x = 0; + edge_said = edge_right; + } else { + spk_x++; + } + spk_pos += 2; + last_state = state; + } + if (edge_said > 0) + announce_edge(vc, edge_said); + say_word(vc); +} + +static void spell_word(struct vc_data *vc) +{ + static char const *delay_str[] = { "", ",", ".", ". .", ". . ." }; + u16 *cp = buf; + char *cp1; + char *str_cap = spk_str_caps_stop; + char *last_cap = spk_str_caps_stop; + struct var_t *direct = spk_get_var(DIRECT); + u16 ch; + + if (!get_word(vc)) + return; + while ((ch = *cp)) { + if (cp != buf) + synth_printf(" %s ", delay_str[spk_spell_delay]); + /* FIXME: Non-latin1 considered as lower case */ + if (ch < 0x100 && IS_CHAR(ch, B_CAP)) { + str_cap = spk_str_caps_start; + if (*spk_str_caps_stop) + spk_pitch_shift++; + else /* synth has no pitch */ + last_cap = spk_str_caps_stop; + } else { + str_cap = spk_str_caps_stop; + } + if (str_cap != last_cap) { + synth_printf("%s", str_cap); + last_cap = str_cap; + } + if (ch >= 0x100 || (direct && direct->u.n.value)) { + synth_putwc_s(ch); + } else if (this_speakup_key == SPELL_PHONETIC && + ch <= 0x7f && isalpha(ch)) { + ch &= 0x1f; + cp1 = phonetic[--ch]; + synth_printf("%s", cp1); + } else { + cp1 = spk_characters[ch]; + if (*cp1 == '^') { + synth_printf("%s", spk_msg_get(MSG_CTRL)); + cp1++; + } + synth_printf("%s", cp1); + } + cp++; + } + if (str_cap != spk_str_caps_stop) + synth_printf("%s", spk_str_caps_stop); +} + +static int get_line(struct vc_data *vc) +{ + u_long tmp = spk_pos - (spk_x * 2); + int i = 0; + u_char tmp2; + + spk_old_attr = spk_attr; + spk_attr = get_attributes(vc, (u_short *)spk_pos); + for (i = 0; i < vc->vc_cols; i++) { + buf[i] = get_char(vc, (u_short *)tmp, &tmp2); + tmp += 2; + } + for (--i; i >= 0; i--) + if (buf[i] != SPACE) + break; + return ++i; +} + +static void say_line(struct vc_data *vc) +{ + int i = get_line(vc); + u16 *cp; + u_short saved_punc_mask = spk_punc_mask; + + if (i == 0) { + synth_printf("%s\n", spk_msg_get(MSG_BLANK)); + return; + } + buf[i++] = '\n'; + if (this_speakup_key == SAY_LINE_INDENT) { + cp = buf; + while (*cp == SPACE) + cp++; + synth_printf("%zd, ", (cp - buf) + 1); + } + spk_punc_mask = spk_punc_masks[spk_reading_punc]; + spkup_write(buf, i); + spk_punc_mask = saved_punc_mask; +} + +static void say_prev_line(struct vc_data *vc) +{ + spk_parked |= 0x01; + if (spk_y == 0) { + announce_edge(vc, edge_top); + return; + } + spk_y--; + spk_pos -= vc->vc_size_row; + say_line(vc); +} + +static void say_next_line(struct vc_data *vc) +{ + spk_parked |= 0x01; + if (spk_y == vc->vc_rows - 1) { + announce_edge(vc, edge_bottom); + return; + } + spk_y++; + spk_pos += vc->vc_size_row; + say_line(vc); +} + +static int say_from_to(struct vc_data *vc, u_long from, u_long to, + int read_punc) +{ + int i = 0; + u_char tmp; + u_short saved_punc_mask = spk_punc_mask; + + spk_old_attr = spk_attr; + spk_attr = get_attributes(vc, (u_short *)from); + while (from < to) { + buf[i++] = get_char(vc, (u_short *)from, &tmp); + from += 2; + if (i >= vc->vc_size_row) + break; + } + for (--i; i >= 0; i--) + if (buf[i] != SPACE) + break; + buf[++i] = SPACE; + buf[++i] = '\0'; + if (i < 1) + return i; + if (read_punc) + spk_punc_mask = spk_punc_info[spk_reading_punc].mask; + spkup_write(buf, i); + if (read_punc) + spk_punc_mask = saved_punc_mask; + return i - 1; +} + +static void say_line_from_to(struct vc_data *vc, u_long from, u_long to, + int read_punc) +{ + u_long start = vc->vc_origin + (spk_y * vc->vc_size_row); + u_long end = start + (to * 2); + + start += from * 2; + if (say_from_to(vc, start, end, read_punc) <= 0) + if (cursor_track != read_all_mode) + synth_printf("%s\n", spk_msg_get(MSG_BLANK)); +} + +/* Sentence Reading Commands */ + +static int currsentence; +static int numsentences[2]; +static u16 *sentbufend[2]; +static u16 *sentmarks[2][10]; +static int currbuf; +static int bn; +static u16 sentbuf[2][256]; + +static int say_sentence_num(int num, int prev) +{ + bn = currbuf; + currsentence = num + 1; + if (prev && --bn == -1) + bn = 1; + + if (num > numsentences[bn]) + return 0; + + spkup_write(sentmarks[bn][num], sentbufend[bn] - sentmarks[bn][num]); + return 1; +} + +static int get_sentence_buf(struct vc_data *vc, int read_punc) +{ + u_long start, end; + int i, bn; + u_char tmp; + + currbuf++; + if (currbuf == 2) + currbuf = 0; + bn = currbuf; + start = vc->vc_origin + ((spk_y) * vc->vc_size_row); + end = vc->vc_origin + ((spk_y) * vc->vc_size_row) + vc->vc_cols * 2; + + numsentences[bn] = 0; + sentmarks[bn][0] = &sentbuf[bn][0]; + i = 0; + spk_old_attr = spk_attr; + spk_attr = get_attributes(vc, (u_short *)start); + + while (start < end) { + sentbuf[bn][i] = get_char(vc, (u_short *)start, &tmp); + if (i > 0) { + if (sentbuf[bn][i] == SPACE && + sentbuf[bn][i - 1] == '.' && + numsentences[bn] < 9) { + /* Sentence Marker */ + numsentences[bn]++; + sentmarks[bn][numsentences[bn]] = + &sentbuf[bn][i]; + } + } + i++; + start += 2; + if (i >= vc->vc_size_row) + break; + } + + for (--i; i >= 0; i--) + if (sentbuf[bn][i] != SPACE) + break; + + if (i < 1) + return -1; + + sentbuf[bn][++i] = SPACE; + sentbuf[bn][++i] = '\0'; + + sentbufend[bn] = &sentbuf[bn][i]; + return numsentences[bn]; +} + +static void say_screen_from_to(struct vc_data *vc, u_long from, u_long to) +{ + u_long start = vc->vc_origin, end; + + if (from > 0) + start += from * vc->vc_size_row; + if (to > vc->vc_rows) + to = vc->vc_rows; + end = vc->vc_origin + (to * vc->vc_size_row); + for (from = start; from < end; from = to) { + to = from + vc->vc_size_row; + say_from_to(vc, from, to, 1); + } +} + +static void say_screen(struct vc_data *vc) +{ + say_screen_from_to(vc, 0, vc->vc_rows); +} + +static void speakup_win_say(struct vc_data *vc) +{ + u_long start, end, from, to; + + if (win_start < 2) { + synth_printf("%s\n", spk_msg_get(MSG_NO_WINDOW)); + return; + } + start = vc->vc_origin + (win_top * vc->vc_size_row); + end = vc->vc_origin + (win_bottom * vc->vc_size_row); + while (start <= end) { + from = start + (win_left * 2); + to = start + (win_right * 2); + say_from_to(vc, from, to, 1); + start += vc->vc_size_row; + } +} + +static void top_edge(struct vc_data *vc) +{ + spk_parked |= 0x01; + spk_pos = vc->vc_origin + 2 * spk_x; + spk_y = 0; + say_line(vc); +} + +static void bottom_edge(struct vc_data *vc) +{ + spk_parked |= 0x01; + spk_pos += (vc->vc_rows - spk_y - 1) * vc->vc_size_row; + spk_y = vc->vc_rows - 1; + say_line(vc); +} + +static void left_edge(struct vc_data *vc) +{ + spk_parked |= 0x01; + spk_pos -= spk_x * 2; + spk_x = 0; + say_char(vc); +} + +static void right_edge(struct vc_data *vc) +{ + spk_parked |= 0x01; + spk_pos += (vc->vc_cols - spk_x - 1) * 2; + spk_x = vc->vc_cols - 1; + say_char(vc); +} + +static void say_first_char(struct vc_data *vc) +{ + int i, len = get_line(vc); + u16 ch; + + spk_parked |= 0x01; + if (len == 0) { + synth_printf("%s\n", spk_msg_get(MSG_BLANK)); + return; + } + for (i = 0; i < len; i++) + if (buf[i] != SPACE) + break; + ch = buf[i]; + spk_pos -= (spk_x - i) * 2; + spk_x = i; + synth_printf("%d, ", ++i); + speak_char(ch); +} + +static void say_last_char(struct vc_data *vc) +{ + int len = get_line(vc); + u16 ch; + + spk_parked |= 0x01; + if (len == 0) { + synth_printf("%s\n", spk_msg_get(MSG_BLANK)); + return; + } + ch = buf[--len]; + spk_pos -= (spk_x - len) * 2; + spk_x = len; + synth_printf("%d, ", ++len); + speak_char(ch); +} + +static void say_position(struct vc_data *vc) +{ + synth_printf(spk_msg_get(MSG_POS_INFO), spk_y + 1, spk_x + 1, + vc->vc_num + 1); + synth_printf("\n"); +} + +/* Added by brianb */ +static void say_char_num(struct vc_data *vc) +{ + u_char tmp; + u16 ch = get_char(vc, (u_short *)spk_pos, &tmp); + + synth_printf(spk_msg_get(MSG_CHAR_INFO), ch, ch); +} + +/* these are stub functions to keep keyboard.c happy. */ + +static void say_from_top(struct vc_data *vc) +{ + say_screen_from_to(vc, 0, spk_y); +} + +static void say_to_bottom(struct vc_data *vc) +{ + say_screen_from_to(vc, spk_y, vc->vc_rows); +} + +static void say_from_left(struct vc_data *vc) +{ + say_line_from_to(vc, 0, spk_x, 1); +} + +static void say_to_right(struct vc_data *vc) +{ + say_line_from_to(vc, spk_x, vc->vc_cols, 1); +} + +/* end of stub functions. */ + +static void spkup_write(const u16 *in_buf, int count) +{ + static int rep_count; + static u16 ch = '\0', old_ch = '\0'; + static u_short char_type, last_type; + int in_count = count; + + spk_keydown = 0; + while (count--) { + if (cursor_track == read_all_mode) { + /* Insert Sentence Index */ + if ((in_buf == sentmarks[bn][currsentence]) && + (currsentence <= numsentences[bn])) + synth_insert_next_index(currsentence++); + } + ch = *in_buf++; + if (ch < 0x100) + char_type = spk_chartab[ch]; + else + char_type = ALPHA; + if (ch == old_ch && !(char_type & B_NUM)) { + if (++rep_count > 2) + continue; + } else { + if ((last_type & CH_RPT) && rep_count > 2) { + synth_printf(" "); + synth_printf(spk_msg_get(MSG_REPEAT_DESC), + ++rep_count); + synth_printf(" "); + } + rep_count = 0; + } + if (ch == spk_lastkey) { + rep_count = 0; + if (spk_key_echo == 1 && ch >= MINECHOCHAR) + speak_char(ch); + } else if (char_type & B_ALPHA) { + if ((synth_flags & SF_DEC) && (last_type & PUNC)) + synth_buffer_add(SPACE); + synth_putwc_s(ch); + } else if (char_type & B_NUM) { + rep_count = 0; + synth_putwc_s(ch); + } else if (char_type & spk_punc_mask) { + speak_char(ch); + char_type &= ~PUNC; /* for dec nospell processing */ + } else if (char_type & SYNTH_OK) { + /* these are usually puncts like . and , which synth + * needs for expression. + * suppress multiple to get rid of long pauses and + * clear repeat count + * so if someone has + * repeats on you don't get nothing repeated count + */ + if (ch != old_ch) + synth_putwc_s(ch); + else + rep_count = 0; + } else { +/* send space and record position, if next is num overwrite space */ + if (old_ch != ch) + synth_buffer_add(SPACE); + else + rep_count = 0; + } + old_ch = ch; + last_type = char_type; + } + spk_lastkey = 0; + if (in_count > 2 && rep_count > 2) { + if (last_type & CH_RPT) { + synth_printf(" "); + synth_printf(spk_msg_get(MSG_REPEAT_DESC2), + ++rep_count); + synth_printf(" "); + } + rep_count = 0; + } +} + +static const int NUM_CTL_LABELS = (MSG_CTL_END - MSG_CTL_START + 1); + +static void read_all_doc(struct vc_data *vc); +static void cursor_done(struct timer_list *unused); +static DEFINE_TIMER(cursor_timer, cursor_done); + +static void do_handle_shift(struct vc_data *vc, u_char value, char up_flag) +{ + unsigned long flags; + + if (!synth || up_flag || spk_killed) + return; + spin_lock_irqsave(&speakup_info.spinlock, flags); + if (cursor_track == read_all_mode) { + switch (value) { + case KVAL(K_SHIFT): + del_timer(&cursor_timer); + spk_shut_up &= 0xfe; + spk_do_flush(); + read_all_doc(vc); + break; + case KVAL(K_CTRL): + del_timer(&cursor_timer); + cursor_track = prev_cursor_track; + spk_shut_up &= 0xfe; + spk_do_flush(); + break; + } + } else { + spk_shut_up &= 0xfe; + spk_do_flush(); + } + if (spk_say_ctrl && value < NUM_CTL_LABELS) + synth_printf("%s", spk_msg_get(MSG_CTL_START + value)); + spin_unlock_irqrestore(&speakup_info.spinlock, flags); +} + +static void do_handle_latin(struct vc_data *vc, u_char value, char up_flag) +{ + unsigned long flags; + + spin_lock_irqsave(&speakup_info.spinlock, flags); + if (up_flag) { + spk_lastkey = 0; + spk_keydown = 0; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return; + } + if (!synth || spk_killed) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return; + } + spk_shut_up &= 0xfe; + spk_lastkey = value; + spk_keydown++; + spk_parked &= 0xfe; + if (spk_key_echo == 2 && value >= MINECHOCHAR) + speak_char(value); + spin_unlock_irqrestore(&speakup_info.spinlock, flags); +} + +int spk_set_key_info(const u_char *key_info, u_char *k_buffer) +{ + int i = 0, states, key_data_len; + const u_char *cp = key_info; + u_char *cp1 = k_buffer; + u_char ch, version, num_keys; + + version = *cp++; + if (version != KEY_MAP_VER) { + pr_debug("version found %d should be %d\n", + version, KEY_MAP_VER); + return -EINVAL; + } + num_keys = *cp; + states = (int)cp[1]; + key_data_len = (states + 1) * (num_keys + 1); + if (key_data_len + SHIFT_TBL_SIZE + 4 >= sizeof(spk_key_buf)) { + pr_debug("too many key_infos (%d over %u)\n", + key_data_len + SHIFT_TBL_SIZE + 4, + (unsigned int)(sizeof(spk_key_buf))); + return -EINVAL; + } + memset(k_buffer, 0, SHIFT_TBL_SIZE); + memset(spk_our_keys, 0, sizeof(spk_our_keys)); + spk_shift_table = k_buffer; + spk_our_keys[0] = spk_shift_table; + cp1 += SHIFT_TBL_SIZE; + memcpy(cp1, cp, key_data_len + 3); + /* get num_keys, states and data */ + cp1 += 2; /* now pointing at shift states */ + for (i = 1; i <= states; i++) { + ch = *cp1++; + if (ch >= SHIFT_TBL_SIZE) { + pr_debug("(%d) not valid shift state (max_allowed = %d)\n", + ch, SHIFT_TBL_SIZE); + return -EINVAL; + } + spk_shift_table[ch] = i; + } + keymap_flags = *cp1++; + while ((ch = *cp1)) { + if (ch >= MAX_KEY) { + pr_debug("(%d), not valid key, (max_allowed = %d)\n", + ch, MAX_KEY); + return -EINVAL; + } + spk_our_keys[ch] = cp1; + cp1 += states + 1; + } + return 0; +} + +static struct var_t spk_vars[] = { + /* bell must be first to set high limit */ + {BELL_POS, .u.n = {NULL, 0, 0, 0, 0, 0, NULL} }, + {SPELL_DELAY, .u.n = {NULL, 0, 0, 4, 0, 0, NULL} }, + {ATTRIB_BLEEP, .u.n = {NULL, 1, 0, 3, 0, 0, NULL} }, + {BLEEPS, .u.n = {NULL, 3, 0, 3, 0, 0, NULL} }, + {BLEEP_TIME, .u.n = {NULL, 30, 1, 200, 0, 0, NULL} }, + {PUNC_LEVEL, .u.n = {NULL, 1, 0, 4, 0, 0, NULL} }, + {READING_PUNC, .u.n = {NULL, 1, 0, 4, 0, 0, NULL} }, + {CURSOR_TIME, .u.n = {NULL, 120, 50, 600, 0, 0, NULL} }, + {SAY_CONTROL, TOGGLE_0}, + {SAY_WORD_CTL, TOGGLE_0}, + {NO_INTERRUPT, TOGGLE_0}, + {KEY_ECHO, .u.n = {NULL, 1, 0, 2, 0, 0, NULL} }, + V_LAST_VAR +}; + +static void toggle_cursoring(struct vc_data *vc) +{ + if (cursor_track == read_all_mode) + cursor_track = prev_cursor_track; + if (++cursor_track >= CT_Max) + cursor_track = 0; + synth_printf("%s\n", spk_msg_get(MSG_CURSOR_MSGS_START + cursor_track)); +} + +void spk_reset_default_chars(void) +{ + int i; + + /* First, free any non-default */ + for (i = 0; i < 256; i++) { + if (spk_characters[i] && + (spk_characters[i] != spk_default_chars[i])) + kfree(spk_characters[i]); + } + + memcpy(spk_characters, spk_default_chars, sizeof(spk_default_chars)); +} + +void spk_reset_default_chartab(void) +{ + memcpy(spk_chartab, default_chartab, sizeof(default_chartab)); +} + +static const struct st_bits_data *pb_edit; + +static int edit_bits(struct vc_data *vc, u_char type, u_char ch, u_short key) +{ + short mask = pb_edit->mask, ch_type = spk_chartab[ch]; + + if (type != KT_LATIN || (ch_type & B_NUM) || ch < SPACE) + return -1; + if (ch == SPACE) { + synth_printf("%s\n", spk_msg_get(MSG_EDIT_DONE)); + spk_special_handler = NULL; + return 1; + } + if (mask < PUNC && !(ch_type & PUNC)) + return -1; + spk_chartab[ch] ^= mask; + speak_char(ch); + synth_printf(" %s\n", + (spk_chartab[ch] & mask) ? spk_msg_get(MSG_ON) : + spk_msg_get(MSG_OFF)); + return 1; +} + +/* Allocation concurrency is protected by the console semaphore */ +static int speakup_allocate(struct vc_data *vc, gfp_t gfp_flags) +{ + int vc_num; + + vc_num = vc->vc_num; + if (!speakup_console[vc_num]) { + speakup_console[vc_num] = kzalloc(sizeof(*speakup_console[0]), + gfp_flags); + if (!speakup_console[vc_num]) + return -ENOMEM; + speakup_date(vc); + } else if (!spk_parked) { + speakup_date(vc); + } + + return 0; +} + +static void speakup_deallocate(struct vc_data *vc) +{ + int vc_num; + + vc_num = vc->vc_num; + kfree(speakup_console[vc_num]); + speakup_console[vc_num] = NULL; +} + +static u_char is_cursor; +static u_long old_cursor_pos, old_cursor_x, old_cursor_y; +static int cursor_con; + +static void reset_highlight_buffers(struct vc_data *); + +static int read_all_key; + +static int in_keyboard_notifier; + +static void start_read_all_timer(struct vc_data *vc, int command); + +enum { + RA_NOTHING, + RA_NEXT_SENT, + RA_PREV_LINE, + RA_NEXT_LINE, + RA_PREV_SENT, + RA_DOWN_ARROW, + RA_TIMER, + RA_FIND_NEXT_SENT, + RA_FIND_PREV_SENT, +}; + +static void kbd_fakekey2(struct vc_data *vc, int command) +{ + del_timer(&cursor_timer); + speakup_fake_down_arrow(); + start_read_all_timer(vc, command); +} + +static void read_all_doc(struct vc_data *vc) +{ + if ((vc->vc_num != fg_console) || !synth || spk_shut_up) + return; + if (!synth_supports_indexing()) + return; + if (cursor_track != read_all_mode) + prev_cursor_track = cursor_track; + cursor_track = read_all_mode; + spk_reset_index_count(0); + if (get_sentence_buf(vc, 0) == -1) { + del_timer(&cursor_timer); + if (!in_keyboard_notifier) + speakup_fake_down_arrow(); + start_read_all_timer(vc, RA_DOWN_ARROW); + } else { + say_sentence_num(0, 0); + synth_insert_next_index(0); + start_read_all_timer(vc, RA_TIMER); + } +} + +static void stop_read_all(struct vc_data *vc) +{ + del_timer(&cursor_timer); + cursor_track = prev_cursor_track; + spk_shut_up &= 0xfe; + spk_do_flush(); +} + +static void start_read_all_timer(struct vc_data *vc, int command) +{ + struct var_t *cursor_timeout; + + cursor_con = vc->vc_num; + read_all_key = command; + cursor_timeout = spk_get_var(CURSOR_TIME); + mod_timer(&cursor_timer, + jiffies + msecs_to_jiffies(cursor_timeout->u.n.value)); +} + +static void handle_cursor_read_all(struct vc_data *vc, int command) +{ + int indcount, sentcount, rv, sn; + + switch (command) { + case RA_NEXT_SENT: + /* Get Current Sentence */ + spk_get_index_count(&indcount, &sentcount); + /*printk("%d %d ", indcount, sentcount); */ + spk_reset_index_count(sentcount + 1); + if (indcount == 1) { + if (!say_sentence_num(sentcount + 1, 0)) { + kbd_fakekey2(vc, RA_FIND_NEXT_SENT); + return; + } + synth_insert_next_index(0); + } else { + sn = 0; + if (!say_sentence_num(sentcount + 1, 1)) { + sn = 1; + spk_reset_index_count(sn); + } else { + synth_insert_next_index(0); + } + if (!say_sentence_num(sn, 0)) { + kbd_fakekey2(vc, RA_FIND_NEXT_SENT); + return; + } + synth_insert_next_index(0); + } + start_read_all_timer(vc, RA_TIMER); + break; + case RA_PREV_SENT: + break; + case RA_NEXT_LINE: + read_all_doc(vc); + break; + case RA_PREV_LINE: + break; + case RA_DOWN_ARROW: + if (get_sentence_buf(vc, 0) == -1) { + kbd_fakekey2(vc, RA_DOWN_ARROW); + } else { + say_sentence_num(0, 0); + synth_insert_next_index(0); + start_read_all_timer(vc, RA_TIMER); + } + break; + case RA_FIND_NEXT_SENT: + rv = get_sentence_buf(vc, 0); + if (rv == -1) + read_all_doc(vc); + if (rv == 0) { + kbd_fakekey2(vc, RA_FIND_NEXT_SENT); + } else { + say_sentence_num(1, 0); + synth_insert_next_index(0); + start_read_all_timer(vc, RA_TIMER); + } + break; + case RA_FIND_PREV_SENT: + break; + case RA_TIMER: + spk_get_index_count(&indcount, &sentcount); + if (indcount < 2) + kbd_fakekey2(vc, RA_DOWN_ARROW); + else + start_read_all_timer(vc, RA_TIMER); + break; + } +} + +static int pre_handle_cursor(struct vc_data *vc, u_char value, char up_flag) +{ + unsigned long flags; + + spin_lock_irqsave(&speakup_info.spinlock, flags); + if (cursor_track == read_all_mode) { + spk_parked &= 0xfe; + if (!synth || up_flag || spk_shut_up) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return NOTIFY_STOP; + } + del_timer(&cursor_timer); + spk_shut_up &= 0xfe; + spk_do_flush(); + start_read_all_timer(vc, value + 1); + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return NOTIFY_STOP; + } + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return NOTIFY_OK; +} + +static void do_handle_cursor(struct vc_data *vc, u_char value, char up_flag) +{ + unsigned long flags; + struct var_t *cursor_timeout; + + spin_lock_irqsave(&speakup_info.spinlock, flags); + spk_parked &= 0xfe; + if (!synth || up_flag || spk_shut_up || cursor_track == CT_Off) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return; + } + spk_shut_up &= 0xfe; + if (spk_no_intr) + spk_do_flush(); +/* the key press flushes if !no_inter but we want to flush on cursor + * moves regardless of no_inter state + */ + is_cursor = value + 1; + old_cursor_pos = vc->vc_pos; + old_cursor_x = vc->state.x; + old_cursor_y = vc->state.y; + speakup_console[vc->vc_num]->ht.cy = vc->state.y; + cursor_con = vc->vc_num; + if (cursor_track == CT_Highlight) + reset_highlight_buffers(vc); + cursor_timeout = spk_get_var(CURSOR_TIME); + mod_timer(&cursor_timer, + jiffies + msecs_to_jiffies(cursor_timeout->u.n.value)); + spin_unlock_irqrestore(&speakup_info.spinlock, flags); +} + +static void update_color_buffer(struct vc_data *vc, const u16 *ic, int len) +{ + int i, bi, hi; + int vc_num = vc->vc_num; + + bi = (vc->vc_attr & 0x70) >> 4; + hi = speakup_console[vc_num]->ht.highsize[bi]; + + i = 0; + if (speakup_console[vc_num]->ht.highsize[bi] == 0) { + speakup_console[vc_num]->ht.rpos[bi] = vc->vc_pos; + speakup_console[vc_num]->ht.rx[bi] = vc->state.x; + speakup_console[vc_num]->ht.ry[bi] = vc->state.y; + } + while ((hi < COLOR_BUFFER_SIZE) && (i < len)) { + if (ic[i] > 32) { + speakup_console[vc_num]->ht.highbuf[bi][hi] = ic[i]; + hi++; + } else if ((ic[i] == 32) && (hi != 0)) { + if (speakup_console[vc_num]->ht.highbuf[bi][hi - 1] != + 32) { + speakup_console[vc_num]->ht.highbuf[bi][hi] = + ic[i]; + hi++; + } + } + i++; + } + speakup_console[vc_num]->ht.highsize[bi] = hi; +} + +static void reset_highlight_buffers(struct vc_data *vc) +{ + int i; + int vc_num = vc->vc_num; + + for (i = 0; i < 8; i++) + speakup_console[vc_num]->ht.highsize[i] = 0; +} + +static int count_highlight_color(struct vc_data *vc) +{ + int i, bg; + int cc; + int vc_num = vc->vc_num; + u16 ch; + u16 *start = (u16 *)vc->vc_origin; + + for (i = 0; i < 8; i++) + speakup_console[vc_num]->ht.bgcount[i] = 0; + + for (i = 0; i < vc->vc_rows; i++) { + u16 *end = start + vc->vc_cols * 2; + u16 *ptr; + + for (ptr = start; ptr < end; ptr++) { + ch = get_attributes(vc, ptr); + bg = (ch & 0x70) >> 4; + speakup_console[vc_num]->ht.bgcount[bg]++; + } + start += vc->vc_size_row; + } + + cc = 0; + for (i = 0; i < 8; i++) + if (speakup_console[vc_num]->ht.bgcount[i] > 0) + cc++; + return cc; +} + +static int get_highlight_color(struct vc_data *vc) +{ + int i, j; + unsigned int cptr[8]; + int vc_num = vc->vc_num; + + for (i = 0; i < 8; i++) + cptr[i] = i; + + for (i = 0; i < 7; i++) + for (j = i + 1; j < 8; j++) + if (speakup_console[vc_num]->ht.bgcount[cptr[i]] > + speakup_console[vc_num]->ht.bgcount[cptr[j]]) + swap(cptr[i], cptr[j]); + + for (i = 0; i < 8; i++) + if (speakup_console[vc_num]->ht.bgcount[cptr[i]] != 0) + if (speakup_console[vc_num]->ht.highsize[cptr[i]] > 0) + return cptr[i]; + return -1; +} + +static int speak_highlight(struct vc_data *vc) +{ + int hc, d; + int vc_num = vc->vc_num; + + if (count_highlight_color(vc) == 1) + return 0; + hc = get_highlight_color(vc); + if (hc != -1) { + d = vc->state.y - speakup_console[vc_num]->ht.cy; + if ((d == 1) || (d == -1)) + if (speakup_console[vc_num]->ht.ry[hc] != vc->state.y) + return 0; + spk_parked |= 0x01; + spk_do_flush(); + spkup_write(speakup_console[vc_num]->ht.highbuf[hc], + speakup_console[vc_num]->ht.highsize[hc]); + spk_pos = spk_cp = speakup_console[vc_num]->ht.rpos[hc]; + spk_x = spk_cx = speakup_console[vc_num]->ht.rx[hc]; + spk_y = spk_cy = speakup_console[vc_num]->ht.ry[hc]; + return 1; + } + return 0; +} + +static void cursor_done(struct timer_list *unused) +{ + struct vc_data *vc = vc_cons[cursor_con].d; + unsigned long flags; + + del_timer(&cursor_timer); + spin_lock_irqsave(&speakup_info.spinlock, flags); + if (cursor_con != fg_console) { + is_cursor = 0; + goto out; + } + speakup_date(vc); + if (win_enabled) { + if (vc->state.x >= win_left && vc->state.x <= win_right && + vc->state.y >= win_top && vc->state.y <= win_bottom) { + spk_keydown = 0; + is_cursor = 0; + goto out; + } + } + if (cursor_track == read_all_mode) { + handle_cursor_read_all(vc, read_all_key); + goto out; + } + if (cursor_track == CT_Highlight) { + if (speak_highlight(vc)) { + spk_keydown = 0; + is_cursor = 0; + goto out; + } + } + if (cursor_track == CT_Window) + speakup_win_say(vc); + else if (is_cursor == 1 || is_cursor == 4) + say_line_from_to(vc, 0, vc->vc_cols, 0); + else + say_char(vc); + spk_keydown = 0; + is_cursor = 0; +out: + spin_unlock_irqrestore(&speakup_info.spinlock, flags); +} + +/* called by: vt_notifier_call() */ +static void speakup_bs(struct vc_data *vc) +{ + unsigned long flags; + + if (!speakup_console[vc->vc_num]) + return; + if (!spin_trylock_irqsave(&speakup_info.spinlock, flags)) + /* Speakup output, discard */ + return; + if (!spk_parked) + speakup_date(vc); + if (spk_shut_up || !synth) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return; + } + if (vc->vc_num == fg_console && spk_keydown) { + spk_keydown = 0; + if (!is_cursor) + say_char(vc); + } + spin_unlock_irqrestore(&speakup_info.spinlock, flags); +} + +/* called by: vt_notifier_call() */ +static void speakup_con_write(struct vc_data *vc, u16 *str, int len) +{ + unsigned long flags; + + if ((vc->vc_num != fg_console) || spk_shut_up || !synth) + return; + if (!spin_trylock_irqsave(&speakup_info.spinlock, flags)) + /* Speakup output, discard */ + return; + if (spk_bell_pos && spk_keydown && (vc->state.x == spk_bell_pos - 1)) + bleep(3); + if ((is_cursor) || (cursor_track == read_all_mode)) { + if (cursor_track == CT_Highlight) + update_color_buffer(vc, str, len); + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return; + } + if (win_enabled) { + if (vc->state.x >= win_left && vc->state.x <= win_right && + vc->state.y >= win_top && vc->state.y <= win_bottom) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return; + } + } + + spkup_write(str, len); + spin_unlock_irqrestore(&speakup_info.spinlock, flags); +} + +static void speakup_con_update(struct vc_data *vc) +{ + unsigned long flags; + + if (!speakup_console[vc->vc_num] || spk_parked) + return; + if (!spin_trylock_irqsave(&speakup_info.spinlock, flags)) + /* Speakup output, discard */ + return; + speakup_date(vc); + if (vc->vc_mode == KD_GRAPHICS && !spk_paused && spk_str_pause[0]) { + synth_printf("%s", spk_str_pause); + spk_paused = true; + } + spin_unlock_irqrestore(&speakup_info.spinlock, flags); +} + +static void do_handle_spec(struct vc_data *vc, u_char value, char up_flag) +{ + unsigned long flags; + int on_off = 2; + char *label; + + if (!synth || up_flag || spk_killed) + return; + spin_lock_irqsave(&speakup_info.spinlock, flags); + spk_shut_up &= 0xfe; + if (spk_no_intr) + spk_do_flush(); + switch (value) { + case KVAL(K_CAPS): + label = spk_msg_get(MSG_KEYNAME_CAPSLOCK); + on_off = vt_get_leds(fg_console, VC_CAPSLOCK); + break; + case KVAL(K_NUM): + label = spk_msg_get(MSG_KEYNAME_NUMLOCK); + on_off = vt_get_leds(fg_console, VC_NUMLOCK); + break; + case KVAL(K_HOLD): + label = spk_msg_get(MSG_KEYNAME_SCROLLLOCK); + on_off = vt_get_leds(fg_console, VC_SCROLLOCK); + if (speakup_console[vc->vc_num]) + speakup_console[vc->vc_num]->tty_stopped = on_off; + break; + default: + spk_parked &= 0xfe; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return; + } + if (on_off < 2) + synth_printf("%s %s\n", + label, spk_msg_get(MSG_STATUS_START + on_off)); + spin_unlock_irqrestore(&speakup_info.spinlock, flags); +} + +static int inc_dec_var(u_char value) +{ + struct st_var_header *p_header; + struct var_t *var_data; + char num_buf[32]; + char *cp = num_buf; + char *pn; + int var_id = (int)value - VAR_START; + int how = (var_id & 1) ? E_INC : E_DEC; + + var_id = var_id / 2 + FIRST_SET_VAR; + p_header = spk_get_var_header(var_id); + if (!p_header) + return -1; + if (p_header->var_type != VAR_NUM) + return -1; + var_data = p_header->data; + if (spk_set_num_var(1, p_header, how) != 0) + return -1; + if (!spk_close_press) { + for (pn = p_header->name; *pn; pn++) { + if (*pn == '_') + *cp = SPACE; + else + *cp++ = *pn; + } + } + snprintf(cp, sizeof(num_buf) - (cp - num_buf), " %d ", + var_data->u.n.value); + synth_printf("%s", num_buf); + return 0; +} + +static void speakup_win_set(struct vc_data *vc) +{ + char info[40]; + + if (win_start > 1) { + synth_printf("%s\n", spk_msg_get(MSG_WINDOW_ALREADY_SET)); + return; + } + if (spk_x < win_left || spk_y < win_top) { + synth_printf("%s\n", spk_msg_get(MSG_END_BEFORE_START)); + return; + } + if (win_start && spk_x == win_left && spk_y == win_top) { + win_left = 0; + win_right = vc->vc_cols - 1; + win_bottom = spk_y; + snprintf(info, sizeof(info), spk_msg_get(MSG_WINDOW_LINE), + (int)win_top + 1); + } else { + if (!win_start) { + win_top = spk_y; + win_left = spk_x; + } else { + win_bottom = spk_y; + win_right = spk_x; + } + snprintf(info, sizeof(info), spk_msg_get(MSG_WINDOW_BOUNDARY), + (win_start) ? + spk_msg_get(MSG_END) : spk_msg_get(MSG_START), + (int)spk_y + 1, (int)spk_x + 1); + } + synth_printf("%s\n", info); + win_start++; +} + +static void speakup_win_clear(struct vc_data *vc) +{ + win_top = 0; + win_bottom = 0; + win_left = 0; + win_right = 0; + win_start = 0; + synth_printf("%s\n", spk_msg_get(MSG_WINDOW_CLEARED)); +} + +static void speakup_win_enable(struct vc_data *vc) +{ + if (win_start < 2) { + synth_printf("%s\n", spk_msg_get(MSG_NO_WINDOW)); + return; + } + win_enabled ^= 1; + if (win_enabled) + synth_printf("%s\n", spk_msg_get(MSG_WINDOW_SILENCED)); + else + synth_printf("%s\n", spk_msg_get(MSG_WINDOW_SILENCE_DISABLED)); +} + +static void speakup_bits(struct vc_data *vc) +{ + int val = this_speakup_key - (FIRST_EDIT_BITS - 1); + + if (spk_special_handler || val < 1 || val > 6) { + synth_printf("%s\n", spk_msg_get(MSG_ERROR)); + return; + } + pb_edit = &spk_punc_info[val]; + synth_printf(spk_msg_get(MSG_EDIT_PROMPT), pb_edit->name); + spk_special_handler = edit_bits; +} + +static int handle_goto(struct vc_data *vc, u_char type, u_char ch, u_short key) +{ + static u_char goto_buf[8]; + static int num; + int maxlen; + char *cp; + u16 wch; + + if (type == KT_SPKUP && ch == SPEAKUP_GOTO) + goto do_goto; + if (type == KT_LATIN && ch == '\n') + goto do_goto; + if (type != 0) + goto oops; + if (ch == 8) { + u16 wch; + + if (num == 0) + return -1; + wch = goto_buf[--num]; + goto_buf[num] = '\0'; + spkup_write(&wch, 1); + return 1; + } + if (ch < '+' || ch > 'y') + goto oops; + wch = ch; + goto_buf[num++] = ch; + goto_buf[num] = '\0'; + spkup_write(&wch, 1); + maxlen = (*goto_buf >= '0') ? 3 : 4; + if ((ch == '+' || ch == '-') && num == 1) + return 1; + if (ch >= '0' && ch <= '9' && num < maxlen) + return 1; + if (num < maxlen - 1 || num > maxlen) + goto oops; + if (ch < 'x' || ch > 'y') { +oops: + if (!spk_killed) + synth_printf(" %s\n", spk_msg_get(MSG_GOTO_CANCELED)); + goto_buf[num = 0] = '\0'; + spk_special_handler = NULL; + return 1; + } + + /* Do not replace with kstrtoul: here we need cp to be updated */ + goto_pos = simple_strtoul(goto_buf, &cp, 10); + + if (*cp == 'x') { + if (*goto_buf < '0') + goto_pos += spk_x; + else if (goto_pos > 0) + goto_pos--; + + if (goto_pos >= vc->vc_cols) + goto_pos = vc->vc_cols - 1; + goto_x = 1; + } else { + if (*goto_buf < '0') + goto_pos += spk_y; + else if (goto_pos > 0) + goto_pos--; + + if (goto_pos >= vc->vc_rows) + goto_pos = vc->vc_rows - 1; + goto_x = 0; + } + goto_buf[num = 0] = '\0'; +do_goto: + spk_special_handler = NULL; + spk_parked |= 0x01; + if (goto_x) { + spk_pos -= spk_x * 2; + spk_x = goto_pos; + spk_pos += goto_pos * 2; + say_word(vc); + } else { + spk_y = goto_pos; + spk_pos = vc->vc_origin + (goto_pos * vc->vc_size_row); + say_line(vc); + } + return 1; +} + +static void speakup_goto(struct vc_data *vc) +{ + if (spk_special_handler) { + synth_printf("%s\n", spk_msg_get(MSG_ERROR)); + return; + } + synth_printf("%s\n", spk_msg_get(MSG_GOTO)); + spk_special_handler = handle_goto; +} + +static void speakup_help(struct vc_data *vc) +{ + spk_handle_help(vc, KT_SPKUP, SPEAKUP_HELP, 0); +} + +static void do_nothing(struct vc_data *vc) +{ + return; /* flush done in do_spkup */ +} + +static u_char key_speakup, spk_key_locked; + +static void speakup_lock(struct vc_data *vc) +{ + if (!spk_key_locked) { + spk_key_locked = 16; + key_speakup = 16; + } else { + spk_key_locked = 0; + key_speakup = 0; + } +} + +typedef void (*spkup_hand) (struct vc_data *); +static spkup_hand spkup_handler[] = { + /* must be ordered same as defines in speakup.h */ + do_nothing, speakup_goto, speech_kill, speakup_shut_up, + speakup_cut, speakup_paste, say_first_char, say_last_char, + say_char, say_prev_char, say_next_char, + say_word, say_prev_word, say_next_word, + say_line, say_prev_line, say_next_line, + top_edge, bottom_edge, left_edge, right_edge, + spell_word, spell_word, say_screen, + say_position, say_attributes, + speakup_off, speakup_parked, say_line, /* this is for indent */ + say_from_top, say_to_bottom, + say_from_left, say_to_right, + say_char_num, speakup_bits, speakup_bits, say_phonetic_char, + speakup_bits, speakup_bits, speakup_bits, + speakup_win_set, speakup_win_clear, speakup_win_enable, speakup_win_say, + speakup_lock, speakup_help, toggle_cursoring, read_all_doc, NULL +}; + +static void do_spkup(struct vc_data *vc, u_char value) +{ + if (spk_killed && value != SPEECH_KILL) + return; + spk_keydown = 0; + spk_lastkey = 0; + spk_shut_up &= 0xfe; + this_speakup_key = value; + if (value < SPKUP_MAX_FUNC && spkup_handler[value]) { + spk_do_flush(); + (*spkup_handler[value]) (vc); + } else { + if (inc_dec_var(value) < 0) + bleep(9); + } +} + +static const char *pad_chars = "0123456789+-*/\015,.?()"; + +static int +speakup_key(struct vc_data *vc, int shift_state, int keycode, u_short keysym, + int up_flag) +{ + unsigned long flags; + int kh; + u_char *key_info; + u_char type = KTYP(keysym), value = KVAL(keysym), new_key = 0; + u_char shift_info, offset; + int ret = 0; + + if (!synth) + return 0; + + spin_lock_irqsave(&speakup_info.spinlock, flags); + tty = vc->port.tty; + if (type >= 0xf0) + type -= 0xf0; + if (type == KT_PAD && + (vt_get_leds(fg_console, VC_NUMLOCK))) { + if (up_flag) { + spk_keydown = 0; + goto out; + } + value = pad_chars[value]; + spk_lastkey = value; + spk_keydown++; + spk_parked &= 0xfe; + goto no_map; + } + if (keycode >= MAX_KEY) + goto no_map; + key_info = spk_our_keys[keycode]; + if (!key_info) + goto no_map; + /* Check valid read all mode keys */ + if ((cursor_track == read_all_mode) && (!up_flag)) { + switch (value) { + case KVAL(K_DOWN): + case KVAL(K_UP): + case KVAL(K_LEFT): + case KVAL(K_RIGHT): + case KVAL(K_PGUP): + case KVAL(K_PGDN): + break; + default: + stop_read_all(vc); + break; + } + } + shift_info = (shift_state & 0x0f) + key_speakup; + offset = spk_shift_table[shift_info]; + if (offset) { + new_key = key_info[offset]; + if (new_key) { + ret = 1; + if (new_key == SPK_KEY) { + if (!spk_key_locked) + key_speakup = (up_flag) ? 0 : 16; + if (up_flag || spk_killed) + goto out; + spk_shut_up &= 0xfe; + spk_do_flush(); + goto out; + } + if (up_flag) + goto out; + if (last_keycode == keycode && + time_after(last_spk_jiffy + MAX_DELAY, jiffies)) { + spk_close_press = 1; + offset = spk_shift_table[shift_info + 32]; + /* double press? */ + if (offset && key_info[offset]) + new_key = key_info[offset]; + } + last_keycode = keycode; + last_spk_jiffy = jiffies; + type = KT_SPKUP; + value = new_key; + } + } +no_map: + if (type == KT_SPKUP && !spk_special_handler) { + do_spkup(vc, new_key); + spk_close_press = 0; + ret = 1; + goto out; + } + if (up_flag || spk_killed || type == KT_SHIFT) + goto out; + spk_shut_up &= 0xfe; + kh = (value == KVAL(K_DOWN)) || + (value == KVAL(K_UP)) || + (value == KVAL(K_LEFT)) || + (value == KVAL(K_RIGHT)); + if ((cursor_track != read_all_mode) || !kh) + if (!spk_no_intr) + spk_do_flush(); + if (spk_special_handler) { + if (type == KT_SPEC && value == 1) { + value = '\n'; + type = KT_LATIN; + } else if (type == KT_LETTER) { + type = KT_LATIN; + } else if (value == 0x7f) { + value = 8; /* make del = backspace */ + } + ret = (*spk_special_handler) (vc, type, value, keycode); + spk_close_press = 0; + if (ret < 0) + bleep(9); + goto out; + } + last_keycode = 0; +out: + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return ret; +} + +static int keyboard_notifier_call(struct notifier_block *nb, + unsigned long code, void *_param) +{ + struct keyboard_notifier_param *param = _param; + struct vc_data *vc = param->vc; + int up = !param->down; + int ret = NOTIFY_OK; + static int keycode; /* to hold the current keycode */ + + in_keyboard_notifier = 1; + + if (vc->vc_mode == KD_GRAPHICS) + goto out; + + /* + * First, determine whether we are handling a fake keypress on + * the current processor. If we are, then return NOTIFY_OK, + * to pass the keystroke up the chain. This prevents us from + * trying to take the Speakup lock while it is held by the + * processor on which the simulated keystroke was generated. + * Also, the simulated keystrokes should be ignored by Speakup. + */ + + if (speakup_fake_key_pressed()) + goto out; + + switch (code) { + case KBD_KEYCODE: + /* speakup requires keycode and keysym currently */ + keycode = param->value; + break; + case KBD_UNBOUND_KEYCODE: + /* not used yet */ + break; + case KBD_UNICODE: + /* not used yet */ + break; + case KBD_KEYSYM: + if (speakup_key(vc, param->shift, keycode, param->value, up)) + ret = NOTIFY_STOP; + else if (KTYP(param->value) == KT_CUR) + ret = pre_handle_cursor(vc, KVAL(param->value), up); + break; + case KBD_POST_KEYSYM:{ + unsigned char type = KTYP(param->value) - 0xf0; + unsigned char val = KVAL(param->value); + + switch (type) { + case KT_SHIFT: + do_handle_shift(vc, val, up); + break; + case KT_LATIN: + case KT_LETTER: + do_handle_latin(vc, val, up); + break; + case KT_CUR: + do_handle_cursor(vc, val, up); + break; + case KT_SPEC: + do_handle_spec(vc, val, up); + break; + } + break; + } + } +out: + in_keyboard_notifier = 0; + return ret; +} + +static int vt_notifier_call(struct notifier_block *nb, + unsigned long code, void *_param) +{ + struct vt_notifier_param *param = _param; + struct vc_data *vc = param->vc; + + switch (code) { + case VT_ALLOCATE: + if (vc->vc_mode == KD_TEXT) + speakup_allocate(vc, GFP_ATOMIC); + break; + case VT_DEALLOCATE: + speakup_deallocate(vc); + break; + case VT_WRITE: + if (param->c == '\b') { + speakup_bs(vc); + } else { + u16 d = param->c; + + speakup_con_write(vc, &d, 1); + } + break; + case VT_UPDATE: + speakup_con_update(vc); + break; + } + return NOTIFY_OK; +} + +/* called by: module_exit() */ +static void __exit speakup_exit(void) +{ + int i; + + unregister_keyboard_notifier(&keyboard_notifier_block); + unregister_vt_notifier(&vt_notifier_block); + speakup_unregister_devsynth(); + speakup_cancel_selection(); + speakup_cancel_paste(); + del_timer_sync(&cursor_timer); + kthread_stop(speakup_task); + speakup_task = NULL; + mutex_lock(&spk_mutex); + synth_release(); + mutex_unlock(&spk_mutex); + spk_ttyio_unregister_ldisc(); + + speakup_kobj_exit(); + + for (i = 0; i < MAX_NR_CONSOLES; i++) + kfree(speakup_console[i]); + + speakup_remove_virtual_keyboard(); + + for (i = 0; i < MAXVARS; i++) + speakup_unregister_var(i); + + for (i = 0; i < 256; i++) { + if (spk_characters[i] != spk_default_chars[i]) + kfree(spk_characters[i]); + } + + spk_free_user_msgs(); +} + +/* call by: module_init() */ +static int __init speakup_init(void) +{ + int i; + long err = 0; + struct vc_data *vc = vc_cons[fg_console].d; + struct var_t *var; + + /* These first few initializations cannot fail. */ + spk_initialize_msgs(); /* Initialize arrays for i18n. */ + spk_reset_default_chars(); + spk_reset_default_chartab(); + spk_strlwr(synth_name); + spk_vars[0].u.n.high = vc->vc_cols; + for (var = spk_vars; var->var_id != MAXVARS; var++) + speakup_register_var(var); + for (var = synth_time_vars; + (var->var_id >= 0) && (var->var_id < MAXVARS); var++) + speakup_register_var(var); + for (i = 1; spk_punc_info[i].mask != 0; i++) + spk_set_mask_bits(NULL, i, 2); + + spk_set_key_info(spk_key_defaults, spk_key_buf); + + /* From here on out, initializations can fail. */ + err = speakup_add_virtual_keyboard(); + if (err) + goto error_virtkeyboard; + + for (i = 0; i < MAX_NR_CONSOLES; i++) + if (vc_cons[i].d) { + err = speakup_allocate(vc_cons[i].d, GFP_KERNEL); + if (err) + goto error_kobjects; + } + + if (spk_quiet_boot) + spk_shut_up |= 0x01; + + err = speakup_kobj_init(); + if (err) + goto error_kobjects; + + spk_ttyio_register_ldisc(); + synth_init(synth_name); + speakup_register_devsynth(); + /* + * register_devsynth might fail, but this error is not fatal. + * /dev/synth is an extra feature; the rest of Speakup + * will work fine without it. + */ + + err = register_keyboard_notifier(&keyboard_notifier_block); + if (err) + goto error_kbdnotifier; + err = register_vt_notifier(&vt_notifier_block); + if (err) + goto error_vtnotifier; + + speakup_task = kthread_create(speakup_thread, NULL, "speakup"); + + if (IS_ERR(speakup_task)) { + err = PTR_ERR(speakup_task); + goto error_task; + } + + set_user_nice(speakup_task, 10); + wake_up_process(speakup_task); + + pr_info("speakup %s: initialized\n", SPEAKUP_VERSION); + pr_info("synth name on entry is: %s\n", synth_name); + goto out; + +error_task: + unregister_vt_notifier(&vt_notifier_block); + +error_vtnotifier: + unregister_keyboard_notifier(&keyboard_notifier_block); + del_timer(&cursor_timer); + +error_kbdnotifier: + speakup_unregister_devsynth(); + mutex_lock(&spk_mutex); + synth_release(); + mutex_unlock(&spk_mutex); + speakup_kobj_exit(); + +error_kobjects: + for (i = 0; i < MAX_NR_CONSOLES; i++) + kfree(speakup_console[i]); + + speakup_remove_virtual_keyboard(); + +error_virtkeyboard: + for (i = 0; i < MAXVARS; i++) + speakup_unregister_var(i); + + for (i = 0; i < 256; i++) { + if (spk_characters[i] != spk_default_chars[i]) + kfree(spk_characters[i]); + } + + spk_free_user_msgs(); + +out: + return err; +} + +module_init(speakup_init); +module_exit(speakup_exit); |