/* 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);

