/* 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. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include /* __get_free_page() and friends */ #include #include #include #include #include #include #include /* for KT_SHIFT */ #include /* for vc_kbd_* and friends */ #include #include /* speakup_*_selection */ #include #include #include #include #include #include #include #include /* 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 "); MODULE_AUTHOR("Daniel Drake "); MODULE_DESCRIPTION("Speakup console speech"); MODULE_LICENSE("GPL"); MODULE_VERSION(SPEAKUP_VERSION); char *synth_name; module_param_named(synth, synth_name, charp, S_IRUGO); module_param_named(quiet, spk_quiet_boot, bool, S_IRUGO); 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 char 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", spk_str_caps_stop[MAXVARLEN + 1] = "\0"; 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 char *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 u_char spk_lastkey, 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(u16 *pos) { return (u_char) (scr_readw(pos) >> 8); } static void speakup_date(struct vc_data *vc) { spk_x = spk_cx = vc->vc_x; spk_y = spk_cy = vc->vc_y; spk_pos = spk_cp = vc->vc_pos; spk_old_attr = spk_attr; spk_attr = get_attributes((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 != NULL) 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(u_char ch) { char *cp = spk_characters[ch]; struct var_t *direct = spk_get_var(DIRECT); if (direct && direct->u.n.value) { if (IS_CHAR(ch, B_CAP)) { spk_pitch_shift++; synth_printf("%s", spk_str_caps_start); } synth_printf("%c", ch); if (IS_CHAR(ch, B_CAP)) synth_printf("%s", spk_str_caps_stop); return; } if (cp == NULL) { pr_info("speak_char: cp == NULL!\n"); return; } synth_buffer_add(SPACE); if (IS_CHAR(ch, B_CAP)) { spk_pitch_shift++; synth_printf("%s", spk_str_caps_start); synth_printf("%s", cp); synth_printf("%s", spk_str_caps_stop); } else { if (*cp == '^') { synth_printf("%s", spk_msg_get(MSG_CTRL)); cp++; } synth_printf("%s", cp); } synth_buffer_add(SPACE); } static u16 get_char(struct vc_data *vc, u16 *pos, u_char *attribs) { u16 ch = ' '; if (vc && pos) { u16 w = scr_readw(pos); u16 c = w & 0xff; if (w & vc->vc_hi_font_mask) c |= 0x100; ch = inverse_translate(vc, c, 0); *attribs = (w & 0xff00) >> 8; } return ch; } static void say_char(struct vc_data *vc) { u_short 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 & 0xff); } static void say_phonetic_char(struct vc_data *vc) { u_short ch; spk_old_attr = spk_attr; ch = get_char(vc, (u_short *) spk_pos, &spk_attr); if (isascii(ch) && isalpha(ch)) { ch &= 0x1f; synth_printf("%s\n", phonetic[--ch]); } else { if (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; char ch; u_short attr_ch; u_char temp; spk_old_attr = spk_attr; ch = (char)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 || IS_WDLM(ch)) && ((char)get_char(vc, (u_short *) &tmp_pos + 1, &temp) > SPACE)) { tmp_pos += 2; tmpx++; } else while (tmpx > 0) { ch = (char)get_char(vc, (u_short *) tmp_pos - 1, &temp); if ((ch == SPACE || ch == 0 || IS_WDLM(ch)) && ((char)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 & 0xff; while (tmpx < vc->vc_cols - 1) { tmp_pos += 2; tmpx++; ch = (char)get_char(vc, (u_short *) tmp_pos, &temp); if ((ch == SPACE) || ch == 0 || (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; char 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 = (char)get_char(vc, (u_short *) spk_pos, &temp); if (ch == SPACE || ch == 0) state = 0; else if (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; char 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 = (char)get_char(vc, (u_short *) spk_pos, &temp); if (ch == SPACE || ch == 0) state = 0; else if (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 *delay_str[] = { "", ",", ".", ". .", ". . ." }; char *cp = buf, *str_cap = spk_str_caps_stop; char *cp1, *last_cap = spk_str_caps_stop; u_char ch; if (!get_word(vc)) return; while ((ch = (u_char) *cp)) { if (cp != buf) synth_printf(" %s ", delay_str[spk_spell_delay]); if (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 (this_speakup_key == SPELL_PHONETIC && (isascii(ch) && isalpha(ch))) { ch &= 31; cp1 = phonetic[--ch]; } 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((u_short *) spk_pos); for (i = 0; i < vc->vc_cols; i++) { buf[i] = (u_char) 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); char *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("%d, ", (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((u_short *) from); while (from < to) { buf[i++] = (char)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 char *sentbufend[2]; static char *sentmarks[2][10]; static int currbuf; static int bn; static char 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((u_short *) start); while (start < end) { sentbuf[bn][i] = (char)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); u_char 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); u_char 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; u_short ch = get_char(vc, (u_short *) spk_pos, &tmp); ch &= 0xff; 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 char *in_buf, int count) { static int rep_count; static u_char 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 = (u_char) *in_buf++; char_type = spk_chartab[ch]; 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_printf("%c", ch); } else if (char_type & B_NUM) { rep_count = 0; synth_printf("%c", 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_printf("%c", 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(u_long data); static DEFINE_TIMER(cursor_timer, cursor_done, 0, 0); static void do_handle_shift(struct vc_data *vc, u_char value, char up_flag) { unsigned long flags; if (synth == NULL || 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 = spk_keydown = 0; spin_unlock_irqrestore(&speakup_info.spinlock, flags); return; } if (synth == NULL || 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) return -1; 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)) return -2; 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) return -3; spk_shift_table[ch] = i; } keymap_flags = *cp1++; while ((ch = *cp1)) { if (ch >= MAX_KEY) return -4; 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] != NULL) && (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) { int vc_num; vc_num = vc->vc_num; if (speakup_console[vc_num] == NULL) { speakup_console[vc_num] = kzalloc(sizeof(*speakup_console[0]), GFP_ATOMIC); if (speakup_console[vc_num] == NULL) 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 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 == NULL || 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) kbd_fakekey2(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 == NULL || 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 == NULL || 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->vc_x; old_cursor_y = vc->vc_y; speakup_console[vc->vc_num]->ht.cy = vc->vc_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 char *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->vc_x; speakup_console[vc_num]->ht.ry[bi] = vc->vc_y; } while ((hi < COLOR_BUFFER_SIZE) && (i < len)) { if ((ic[i] > 32) && (ic[i] < 127)) { 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(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], tmp; 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]]) { tmp = cptr[i]; cptr[i] = cptr[j]; cptr[j] = tmp; } 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->vc_y - speakup_console[vc_num]->ht.cy; if ((d == 1) || (d == -1)) if (speakup_console[vc_num]->ht.ry[hc] != vc->vc_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(u_long data) { 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->vc_x >= win_left && vc->vc_x <= win_right && vc->vc_y >= win_top && vc->vc_y <= win_bottom) { spk_keydown = 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 = 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 = 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 == NULL) { 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, const char *str, int len) { unsigned long flags; if ((vc->vc_num != fg_console) || spk_shut_up || synth == NULL) return; if (!spin_trylock_irqsave(&speakup_info.spinlock, flags)) /* Speakup output, discard */ return; if (spk_bell_pos && spk_keydown && (vc->vc_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->vc_x >= win_left && vc->vc_x <= win_right && vc->vc_y >= win_top && vc->vc_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] == NULL || spk_parked) return; if (!spin_trylock_irqsave(&speakup_info.spinlock, flags)) /* Speakup output, discard */ return; speakup_date(vc); 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 == NULL || 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 == NULL) 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 = win_bottom = 0; win_left = 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 != NULL || 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, go_pos; char *cp; 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) { if (num == 0) return -1; ch = goto_buf[--num]; goto_buf[num] = '\0'; spkup_write(&ch, 1); return 1; } if (ch < '+' || ch > 'y') goto oops; goto_buf[num++] = ch; goto_buf[num] = '\0'; spkup_write(&ch, 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; } go_pos = kstrtol(goto_buf, 10, (long *)&cp); goto_pos = (u_long) go_pos; if (*cp == 'x') { if (*goto_buf < '0') goto_pos += spk_x; else goto_pos--; if (goto_pos < 0) goto_pos = 0; 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 goto_pos--; if (goto_pos < 0) goto_pos = 0; 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 != NULL) { synth_printf("%s\n", spk_msg_get(MSG_ERROR)); return; } synth_printf("%s\n", spk_msg_get(MSG_GOTO)); spk_special_handler = handle_goto; return; } 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 = key_speakup = 16; else spk_key_locked = 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 == NULL) 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 = spk_lastkey = pad_chars[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 && 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 == NULL) { 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 */ if (vc->vc_mode == KD_GRAPHICS) return ret; /* * 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()) return ret; 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; } } 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); break; case VT_DEALLOCATE: speakup_deallocate(vc); break; case VT_WRITE: if (param->c == '\b') speakup_bs(vc); else if (param->c < 0x100) { char 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_paste(); del_timer(&cursor_timer); kthread_stop(speakup_task); speakup_task = NULL; mutex_lock(&spk_mutex); synth_release(); mutex_unlock(&spk_mutex); 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 st_spk_t *first_console; 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; first_console = kzalloc(sizeof(*first_console), GFP_KERNEL); if (!first_console) { err = -ENOMEM; goto error_alloc; } speakup_console[vc->vc_num] = first_console; speakup_date(vc); for (i = 0; i < MAX_NR_CONSOLES; i++) if (vc_cons[i].d) { err = speakup_allocate(vc_cons[i].d); if (err) goto error_kobjects; } if (spk_quiet_boot) spk_shut_up |= 0x01; err = speakup_kobj_init(); if (err) goto error_kobjects; 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]); error_alloc: 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);