diff options
author | William Hubbs <w.d.hubbs@gmail.com> | 2010-10-07 13:20:02 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2010-10-07 19:22:31 -0700 |
commit | c6e3fd22cd538365bfeb82997d5b89562e077d42 (patch) | |
tree | 5eaa170c9003abc7b24ab340ccabe889cba47992 /drivers/staging/speakup/main.c | |
parent | e59fe083f683ca2ca56abefad290d110808a6fb5 (diff) | |
download | linux-c6e3fd22cd538365bfeb82997d5b89562e077d42.tar.bz2 |
Staging: add speakup to the staging directory
Speakup is a kernel based screen review package for the linux operating
system. It allows blind users to interact with applications on the
linux console by means of synthetic speech.
The authors and maintainers of this code include the following:
Kirk Reiser, Andy Berdan, John Covici, Brian and
David Borowski, Christopher Brannon, Samuel Thibault and William Hubbs.
Signed-off-by: William Hubbs <w.d.hubbs@gmail.com>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/staging/speakup/main.c')
-rw-r--r-- | drivers/staging/speakup/main.c | 2304 |
1 files changed, 2304 insertions, 0 deletions
diff --git a/drivers/staging/speakup/main.c b/drivers/staging/speakup/main.c new file mode 100644 index 000000000000..8c549650f217 --- /dev/null +++ b/drivers/staging/speakup/main.c @@ -0,0 +1,2304 @@ +/* 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 <linux/kernel.h> +#include <linux/version.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> + +#include <linux/bootmem.h> /* for alloc_bootmem */ + +/* 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, S_IRUGO); +module_param_named(quiet, 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 special_handler; + +short pitch_shift, synth_flags; +static char buf[256]; +int attrib_bleep, bleeps, bleep_time = 10; +int no_intr, spell_delay; +int key_echo, say_word_ctl; +int say_ctrl, bell_pos; +short punc_mask; +int punc_level, reading_punc; +char str_caps_start[MAXVARLEN+1] = "\0", str_caps_stop[MAXVARLEN+1] = "\0"; +const struct st_bits_data punc_info[] = { + { "none", "", 0 }, + { "some", "/$%&@", SOME }, + { "most", "$%&#()=+*/@^<>|\\", MOST }, + { "all", "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", PUNC }, + { "delimiters", "", B_WDLM }, + { "repeats", "()", CH_RPT }, + { "extended numeric", "", B_EXNUM }, + { "symbols", "", B_SYM }, + { 0, 0 } +}; +static char mark_cut_flag; +#define MAX_KEY 160 +u_char *our_keys[MAX_KEY], *shift_table; +u_char key_buf[600]; +const u_char 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 *characters[256]; + +char *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, B_SYM, /* 128-135 */ +B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_CAPSYM, /* 136-143 */ +B_CAPSYM, B_CAPSYM, B_SYM, B_CAPSYM, B_SYM, B_SYM, B_SYM, B_SYM, /* 144-151 */ +B_SYM, B_SYM, B_CAPSYM, B_CAPSYM, B_SYM, B_SYM, B_SYM, B_SYM, /* 152-159 */ +WDLM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_CAPSYM, B_SYM, /* 160-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 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); + +struct notifier_block keyboard_notifier_block = { + .notifier_call = keyboard_notifier_call, +}; + +static int vt_notifier_call(struct notifier_block *, + unsigned long code, void *param); + +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 = bleep_time; + freq = vals[val%12]; + if (val > 11) + freq *= (1 << (val/12)); + unprocessed_sound.freq = freq; + unprocessed_sound.jiffies = msecs_to_jiffies(time); + 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) + 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", msg_get(MSG_IAM_ALIVE)); + } else { + synth_printf("%s\n", 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", msg_get(MSG_HEY_THATS_BETTER)); + } else { + spk_shut_up |= 0x80; + synth_printf("%s\n", 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", msg_get(MSG_UNPARKED)); + } else { + spk_parked |= 0x80; + synth_printf("%s\n", 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; + xs = (u_short) spk_x; + ys = (u_short) spk_y; + spk_sel_cons = vc; + synth_printf("%s\n", msg_get(MSG_MARK)); + return; + } + xe = (u_short) spk_x; + ye = (u_short) spk_y; + mark_cut_flag = 0; + synth_printf("%s\n", 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", msg_get(MSG_MARK_CLEARED)); + } else { + synth_printf("%s\n", 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 ", msg_get(MSG_BRIGHT)); + fg -= 8; + } + synth_printf("%s", msg_get(MSG_COLORS_START + fg)); + if (bg > 7) { + synth_printf(" %s ", msg_get(MSG_ON_BLINKING)); + bg -= 8; + } else + synth_printf(" %s ", msg_get(MSG_ON)); + synth_printf("%s\n", 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 (bleeps & 1) + bleep(spk_y); + if ((bleeps & 2) && (msg_id < edge_quiet)) + synth_printf("%s\n", msg_get(MSG_EDGE_MSGS_START + msg_id - 1)); +} + +static void speak_char(u_char ch) +{ + char *cp = characters[ch]; + struct var_t *direct = get_var(DIRECT); + if (direct && direct->u.n.value) { + if (IS_CHAR(ch, B_CAP)) { + pitch_shift++; + synth_printf("%s", str_caps_start); + } + synth_printf("%c", ch); + if (IS_CHAR(ch, B_CAP)) + synth_printf("%s", 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)) { + pitch_shift++; + synth_printf("%s", str_caps_start); + synth_printf("%s", cp); + synth_printf("%s", str_caps_stop); + } else { + if (*cp == '^') { + synth_printf("%s", 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 (attrib_bleep & 1) + bleep(spk_y); + if (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 ", 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 say_word_ctl is true it will + return the word space. If 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 (say_word_ctl && ch == SPACE) { + *buf = '\0'; + synth_printf("%s\n", 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 = punc_mask; + if (cnt == 0) + return; + punc_mask = PUNC; + buf[cnt++] = SPACE; + spkup_write(buf, cnt); + 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 = str_caps_stop; + char *cp1, *last_cap = str_caps_stop; + u_char ch; + if (!get_word(vc)) + return; + while ((ch = (u_char) *cp)) { + if (cp != buf) + synth_printf(" %s ", delay_str[spell_delay]); + if (IS_CHAR(ch, B_CAP)) { + str_cap = str_caps_start; + if (*str_caps_stop) + pitch_shift++; + else /* synth has no pitch */ + last_cap = str_caps_stop; + } else + str_cap = 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 = characters[ch]; + if (*cp1 == '^') { + synth_printf("%s", msg_get(MSG_CTRL)); + cp1++; + } + } + synth_printf("%s", cp1); + cp++; + } + if (str_cap != str_caps_stop) + synth_printf("%s", 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 = punc_mask; + if (i == 0) { + synth_printf("%s\n", msg_get(MSG_BLANK)); + return; + } + buf[i++] = '\n'; + if (this_speakup_key == SAY_LINE_INDENT) { + for (cp = buf; *cp == SPACE; cp++) + ; + synth_printf("%d, ", (cp - buf) + 1); + } + punc_mask = punc_masks[reading_punc]; + spkup_write(buf, i); + 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 = 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) + punc_mask = punc_info[reading_punc].mask; + spkup_write(buf, i); + if (read_punc) + 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", msg_get(MSG_BLANK)); +} + +/* Sentence Reading Commands */ + +void synth_insert_next_index(int); + +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", 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", 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", 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(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(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 = 0; + static u_char ch = '\0', old_ch = '\0'; + static u_short char_type = 0, last_type = 0; + 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(msg_get(MSG_REPEAT_DESC), ++rep_count); + synth_printf(" "); + } + rep_count = 0; + } + if (ch == spk_lastkey) { + rep_count = 0; + if (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&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 pausesand 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(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; + spk_lock(flags); + if (cursor_track == read_all_mode) { + switch (value) { + case KVAL(K_SHIFT): + del_timer(&cursor_timer); + spk_shut_up &= 0xfe; + do_flush(); + read_all_doc(vc); + break; + case KVAL(K_CTRL): + del_timer(&cursor_timer); + cursor_track = prev_cursor_track; + spk_shut_up &= 0xfe; + do_flush(); + break; + } + } else { + spk_shut_up &= 0xfe; + do_flush(); + } + if (say_ctrl && value < NUM_CTL_LABELS) + synth_printf("%s", msg_get(MSG_CTL_START + value)); + spk_unlock(flags); +} + +static void do_handle_latin(struct vc_data *vc, u_char value, char up_flag) +{ + unsigned long flags; + spk_lock(flags); + if (up_flag) { + spk_lastkey = spk_keydown = 0; + spk_unlock(flags); + return; + } + if (synth == NULL || spk_killed) { + spk_unlock(flags); + return; + } + spk_shut_up &= 0xfe; + spk_lastkey = value; + spk_keydown++; + spk_parked &= 0xfe; + if (key_echo == 2 && value >= MINECHOCHAR) + speak_char(value); + spk_unlock(flags); +} + +int 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(key_buf)) + return -2; + memset(k_buffer, 0, SHIFT_TBL_SIZE); + memset(our_keys, 0, sizeof(our_keys)); + shift_table = k_buffer; + our_keys[0] = 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; + shift_table[ch] = i; + } + keymap_flags = *cp1++; + while ((ch = *cp1)) { + if (ch >= MAX_KEY) + return -4; + 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", msg_get(MSG_CURSOR_MSGS_START + cursor_track)); +} + +void reset_default_chars(void) +{ + int i; + + /* First, free any non-default */ + for (i = 0; i < 256; i++) { + if ((characters[i] != NULL) + && (characters[i] != default_chars[i])) + kfree(characters[i]); + } + + memcpy(characters, default_chars, sizeof(default_chars)); +} + +void reset_default_chartab(void) +{ + memcpy(spk_chartab, default_chartab, sizeof(default_chartab)); +} + +static const struct st_bits_data *pb_edit = NULL; + +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", msg_get(MSG_EDIT_DONE)); + 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) ? msg_get(MSG_ON) : msg_get(MSG_OFF)); + return 1; +} + +/* Allocation concurrency is protected by the console semaphore */ +void 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; + speakup_date(vc); + } else if (!spk_parked) + speakup_date(vc); +} + +void speakup_deallocate(struct vc_data *vc) +{ + int vc_num; + + vc_num = vc->vc_num; + if (speakup_console[vc_num] != NULL) { + 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; + +void reset_index_count(int); +void get_index_count(int *, int *); +/*int synth_supports_indexing(void); */ +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; + 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; + 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 = 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 */ + get_index_count(&indcount, &sentcount); + /*printk("%d %d ", indcount, sentcount); */ + 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; + 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: + 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; + spk_lock(flags); + if (cursor_track == read_all_mode) { + spk_parked &= 0xfe; + if (synth == NULL || up_flag || spk_shut_up) { + spk_unlock(flags); + return NOTIFY_STOP; + } + del_timer(&cursor_timer); + spk_shut_up &= 0xfe; + do_flush(); + start_read_all_timer(vc, value+1); + spk_unlock(flags); + return NOTIFY_STOP; + } + spk_unlock(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; + + spk_lock(flags); + spk_parked &= 0xfe; + if (synth == NULL || up_flag || spk_shut_up || cursor_track == CT_Off) { + spk_unlock(flags); + return; + } + spk_shut_up &= 0xfe; + if (no_intr) + 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 = get_var(CURSOR_TIME); + mod_timer(&cursor_timer, jiffies + msecs_to_jiffies(cursor_timeout->u.n.value)); + spk_unlock(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; + 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); + spk_lock(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: + spk_unlock(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 (!spk_trylock(flags)) + /* Speakup output, discard */ + return; + if (!spk_parked) + speakup_date(vc); + if (spk_shut_up || synth == NULL) { + spk_unlock(flags); + return; + } + if (vc->vc_num == fg_console && spk_keydown) { + spk_keydown = 0; + if (!is_cursor) + say_char(vc); + } + spk_unlock(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 (!spk_trylock(flags)) + /* Speakup output, discard */ + return; + if (bell_pos && spk_keydown && (vc->vc_x == bell_pos - 1)) + bleep(3); + if ((is_cursor) || (cursor_track == read_all_mode)) { + if (cursor_track == CT_Highlight) + update_color_buffer(vc, str, len); + spk_unlock(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) { + spk_unlock(flags); + return; + } + } + + spkup_write(str, len); + spk_unlock(flags); +} + +void +speakup_con_update(struct vc_data *vc) +{ + unsigned long flags; + if (speakup_console[vc->vc_num] == NULL || spk_parked) + return; + if (!spk_trylock(flags)) + /* Speakup output, discard */ + return; + speakup_date(vc); + spk_unlock(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; + spk_lock(flags); + spk_shut_up &= 0xfe; + if (no_intr) + do_flush(); + switch (value) { + case KVAL(K_CAPS): + label = msg_get(MSG_KEYNAME_CAPSLOCK); + on_off = (vc_kbd_led(kbd_table + vc->vc_num, VC_CAPSLOCK)); + break; + case KVAL(K_NUM): + label = msg_get(MSG_KEYNAME_NUMLOCK); + on_off = (vc_kbd_led(kbd_table + vc->vc_num, VC_NUMLOCK)); + break; + case KVAL(K_HOLD): + label = msg_get(MSG_KEYNAME_SCROLLLOCK); + on_off = (vc_kbd_led(kbd_table + vc->vc_num, VC_SCROLLOCK)); + if (speakup_console[vc->vc_num]) + speakup_console[vc->vc_num]->tty_stopped = on_off; + break; + default: + spk_parked &= 0xfe; + spk_unlock(flags); + return; + } + if (on_off < 2) + synth_printf("%s %s\n", + label, msg_get(MSG_STATUS_START + on_off)); + spk_unlock(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 = 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 (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", msg_get(MSG_WINDOW_ALREADY_SET)); + return; + } + if (spk_x < win_left || spk_y < win_top) { + synth_printf("%s\n", 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), 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), msg_get(MSG_WINDOW_BOUNDARY), + (win_start) ? msg_get(MSG_END) : 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", msg_get(MSG_WINDOW_CLEARED)); +} + +static void +speakup_win_enable(struct vc_data *vc) +{ + if (win_start < 2) { + synth_printf("%s\n", msg_get(MSG_NO_WINDOW)); + return; + } + win_enabled ^= 1; + if (win_enabled) + synth_printf("%s\n", msg_get(MSG_WINDOW_SILENCED)); + else + synth_printf("%s\n", msg_get(MSG_WINDOW_SILENCE_DISABLED)); +} + +static void +speakup_bits(struct vc_data *vc) +{ + int val = this_speakup_key - (FIRST_EDIT_BITS - 1); + if (special_handler != NULL || val < 1 || val > 6) { + synth_printf("%s\n", msg_get(MSG_ERROR)); + return; + } + pb_edit = &punc_info[val]; + synth_printf(msg_get(MSG_EDIT_PROMPT), pb_edit->name); + 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 = "\0\0\0\0\0\0"; + static int num = 0; + 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", msg_get(MSG_GOTO_CANCELED)); + goto_buf[num = 0] = '\0'; + special_handler = NULL; + return 1; + } + cp = speakup_s2i(goto_buf, &go_pos); + 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: + 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 (special_handler != NULL) { + synth_printf("%s\n", msg_get(MSG_ERROR)); + return; + } + synth_printf("%s\n", msg_get(MSG_GOTO)); + special_handler = handle_goto; + return; +} + +static void speakup_help(struct vc_data *vc) +{ + 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 *); +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]) { + do_flush(); + (*spkup_handler[value])(vc); + } else { + if (inc_dec_var(value) < 0) + bleep(9); + } +} + +static const char *pad_chars = "0123456789+-*/\015,.?()"; + +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; + + spk_lock(flags); + tty = vc->vc_tty; + if (type >= 0xf0) + type -= 0xf0; + if (type == KT_PAD && + (vc_kbd_led(kbd_table + 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 = our_keys[keycode]; + if (key_info == 0) + 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 = 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; + do_flush(); + goto out; + } + if (up_flag) + goto out; + if (last_keycode == keycode && + last_spk_jiffy+MAX_DELAY > jiffies) { + spk_close_press = 1; + offset = 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 && 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 (!no_intr) + do_flush(); + if (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 = (*special_handler)(vc, type, value, keycode); + spk_close_press = 0; + if (ret < 0) + bleep(9); + goto out; + } + last_keycode = 0; +out: + spk_unlock(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; + + free_user_msgs(); + unregister_keyboard_notifier(&keyboard_notifier_block); + unregister_vt_notifier(&vt_notifier_block); + speakup_unregister_devsynth(); + del_timer(&cursor_timer); + + kthread_stop(speakup_task); + speakup_task = NULL; + mutex_lock(&spk_mutex); + synth_release(); + mutex_unlock(&spk_mutex); + + for (i = 0; i < MAXVARS; i++) + speakup_unregister_var(i); + + for (i = 0; i < 256; i++) { + if (characters[i] != default_chars[i]) + kfree(characters[i]); + } + for (i = 0; speakup_console[i]; i++) + kfree(speakup_console[i]); + speakup_kobj_exit(); + speakup_remove_virtual_keyboard(); +} + +/* call by: module_init() */ +static int __init speakup_init(void) +{ + int i; + int err; + struct st_spk_t *first_console; + struct vc_data *vc = vc_cons[fg_console].d; + struct var_t *var; + + err = speakup_add_virtual_keyboard(); + if (err) + return err; + + initialize_msgs(); /* Initialize arrays for i18n. */ + first_console = kzalloc(sizeof(*first_console), GFP_KERNEL); + if (!first_console) + return -ENOMEM; + if (speakup_kobj_init() < 0) + return -ENOMEM; + + reset_default_chars(); + reset_default_chartab(); + + speakup_console[vc->vc_num] = first_console; + speakup_date(vc); + pr_info("speakup %s: initialized\n", SPEAKUP_VERSION); + + 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; punc_info[i].mask != 0; i++) + set_mask_bits(0, i, 2); + + set_key_info(key_defaults, key_buf); + if (quiet_boot) + spk_shut_up |= 0x01; + + for (i = 0; i < MAX_NR_CONSOLES; i++) + if (vc_cons[i].d) + speakup_allocate(vc_cons[i].d); + + pr_warn("synth name on entry is: %s\n", synth_name); + synth_init(synth_name); + speakup_register_devsynth(); + + register_keyboard_notifier(&keyboard_notifier_block); + register_vt_notifier(&vt_notifier_block); + + speakup_task = kthread_create(speakup_thread, NULL, "speakup"); + set_user_nice(speakup_task, 10); + if ( ! IS_ERR(speakup_task)) + wake_up_process(speakup_task); + else + return -ENOMEM; + return 0; +} + + +module_init(speakup_init); +module_exit(speakup_exit); + |