main.c 58 KB
Newer Older
1
/* speakup.c
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 * 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
23
24
25
26
27
*/

#include <linux/kernel.h>
#include <linux/vt.h>
#include <linux/tty.h>
28
#include <linux/mm.h>		/* __get_free_page() and friends */
29
30
31
32
33
34
35
#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 */
36
#include <linux/kbd_kern.h>	/* for vc_kbd_* and friends */
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <linux/input.h>
#include <linux/kmod.h>

/* speakup_*_selection */
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/consolemap.h>

#include <linux/spinlock.h>
#include <linux/notifier.h>

50
#include <linux/uaccess.h>	/* copy_from|to|user() and others */
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

#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);
66
module_param_named(quiet, spk_quiet_boot, bool, S_IRUGO);
67
68
69
70

MODULE_PARM_DESC(synth, "Synth to start if speakup is built in.");
MODULE_PARM_DESC(quiet, "Do not announce when the synthesizer is found.");

71
special_func spk_special_handler;
72

73
short spk_pitch_shift, synth_flags;
74
static char buf[256];
75
76
77
78
79
80
int spk_attrib_bleep, spk_bleeps, spk_bleep_time = 10;
int spk_no_intr, spk_spell_delay;
int spk_key_echo, spk_say_word_ctl;
int spk_say_ctrl, spk_bell_pos;
short spk_punc_mask;
int spk_punc_level, spk_reading_punc;
81
82
char spk_str_caps_start[MAXVARLEN + 1] = "\0";
char spk_str_caps_stop[MAXVARLEN + 1] = "\0";
83
const struct st_bits_data spk_punc_info[] = {
84
85
86
87
88
89
90
91
	{"none", "", 0},
	{"some", "/$%&@", SOME},
	{"most", "$%&#()=+*/@^<>|\\", MOST},
	{"all", "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", PUNC},
	{"delimiters", "", B_WDLM},
	{"repeats", "()", CH_RPT},
	{"extended numeric", "", B_EXNUM},
	{"symbols", "", B_SYM},
92
	{NULL, NULL}
93
};
94

95
96
static char mark_cut_flag;
#define MAX_KEY 160
97
98
static u_char *spk_shift_table;
u_char *spk_our_keys[MAX_KEY];
99
100
u_char spk_key_buf[600];
const u_char spk_key_defaults[] = {
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#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",
123
124
	"india", "juliett", "keelo", "leema", "mike", "november", "oscar",
	    "papa",
125
126
127
128
129
130
131
	"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 */
132
char *spk_characters[256];
133

134
char *spk_default_chars[256] = {
135
/*000*/ "null", "^a", "^b", "^c", "^d", "^e", "^f", "^g",
136
137
/*008*/ "^h", "^i", "^j", "^k", "^l", "^m", "^n", "^o",
/*016*/ "^p", "^q", "^r", "^s", "^t", "^u", "^v", "^w",
138
139
140
141
142
143
/*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",
144
145
146
147
148
149
150
	"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",
151
152
/*089*/ "Y", "ZED", "left bracket", "backslash", "right bracket",
	    "caret",
153
154
155
156
157
	"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",
158
159
160
161
162
163
164
/*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",
165
/*160*/ "nbsp", "inverted bang",
166
167
/*162*/ "cents", "pounds", "currency", "yen", "broken bar", "section",
/*168*/ "diaeresis", "copyright", "female ordinal", "double left angle",
168
/*172*/ "not", "soft hyphen", "registered", "macron",
169
170
/*176*/ "degrees", "plus or minus", "super two", "super three",
/*180*/ "acute accent", "micro", "pilcrow", "middle dot",
171
/*184*/ "cedilla", "super one", "male ordinal", "double right angle",
172
173
174
175
176
177
178
179
/*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",
180
/*210*/ "O GRAVE", "O ACUTE", "O CIRCUMFLEX", "O TILDE", "O OOMLAUT",
181
182
/*215*/ "multiplied by", "O STROKE", "U GRAVE", "U ACUTE",
	    "U CIRCUMFLEX",
183
184
185
/*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",
186
187
188
189
190
191
/*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",
192
193
194
195
196
197
198
199
200
/* 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] = {
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
	B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL,	/* 0-7 */
	B_CTL, B_CTL, A_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL,	/* 8-15 */
	B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL,	/*16-23 */
	B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL,	/* 24-31 */
	WDLM, A_PUNC, PUNC, PUNC, PUNC, PUNC, PUNC, A_PUNC,	/*  !"#$%&' */
	PUNC, PUNC, PUNC, PUNC, A_PUNC, A_PUNC, A_PUNC, PUNC,	/* ()*+, -./ */
	NUM, NUM, NUM, NUM, NUM, NUM, NUM, NUM,	/* 01234567 */
	NUM, NUM, A_PUNC, PUNC, PUNC, PUNC, PUNC, A_PUNC,	/* 89:;<=>? */
	PUNC, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP,	/* @ABCDEFG */
	A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP,	/* HIJKLMNO */
	A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP,	/* PQRSTUVW */
	A_CAP, A_CAP, A_CAP, PUNC, PUNC, PUNC, PUNC, PUNC,	/* XYZ[\]^_ */
	PUNC, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA,	/* `abcdefg */
	ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA,	/* hijklmno */
	ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA,	/* pqrstuvw */
	ALPHA, ALPHA, ALPHA, PUNC, PUNC, PUNC, PUNC, 0,	/* xyz{|}~ */
	B_CAPSYM, B_CAPSYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, /* 128-134 */
	B_SYM,	/* 135 */
	B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, /* 136-142 */
	B_CAPSYM,	/* 143 */
	B_CAPSYM, B_CAPSYM, B_SYM, B_CAPSYM, B_SYM, B_SYM, B_SYM, /* 144-150 */
	B_SYM,	/* 151 */
	B_SYM, B_SYM, B_CAPSYM, B_CAPSYM, B_SYM, B_SYM, B_SYM, /*152-158 */
	B_SYM,	/* 159 */
	WDLM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_CAPSYM, /* 160-166 */
	B_SYM,	/* 167 */
	B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM,	/* 168-175 */
	B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM,	/* 176-183 */
	B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM,	/* 184-191 */
	A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP,	/* 192-199 */
	A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP,	/* 200-207 */
	A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, B_SYM,	/* 208-215 */
	A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, ALPHA,	/* 216-223 */
	ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA,	/* 224-231 */
	ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA,	/* 232-239 */
	ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, B_SYM,	/* 240-247 */
	ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA	/* 248-255 */
238
239
240
};

struct task_struct *speakup_task;
241
struct bleep spk_unprocessed_sound;
242
243
244
245
246
247
248
249
250
251
252
253
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);

254
static struct notifier_block keyboard_notifier_block = {
255
256
257
258
259
260
	.notifier_call = keyboard_notifier_call,
};

static int vt_notifier_call(struct notifier_block *,
			    unsigned long code, void *param);

261
static struct notifier_block vt_notifier_block = {
262
263
264
265
266
	.notifier_call = vt_notifier_call,
};

static unsigned char get_attributes(u16 *pos)
{
267
	return (u_char) (scr_readw(pos) >> 8);
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
}

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;
285
	int time = spk_bleep_time;
286
	freq = vals[val % 12];
287
	if (val > 11)
288
		freq *= (1 << (val / 12));
289
290
291
	spk_unprocessed_sound.freq = freq;
	spk_unprocessed_sound.jiffies = msecs_to_jiffies(time);
	spk_unprocessed_sound.active = 1;
292
293
294
295
296
297
298
299
300
301
302
	/* 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)
303
		spk_do_flush();
304
305
306
307
308
309
310
311
312
313
314
315
}

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;
316
		synth_printf("%s\n", spk_msg_get(MSG_IAM_ALIVE));
317
	} else {
318
		synth_printf("%s\n", spk_msg_get(MSG_YOU_KILLED_SPEAKUP));
319
320
321
322
323
324
325
326
		spk_shut_up |= 0x40;
	}
}

static void speakup_off(struct vc_data *vc)
{
	if (spk_shut_up & 0x80) {
		spk_shut_up &= 0x7f;
327
		synth_printf("%s\n", spk_msg_get(MSG_HEY_THATS_BETTER));
328
329
	} else {
		spk_shut_up |= 0x80;
330
		synth_printf("%s\n", spk_msg_get(MSG_YOU_TURNED_ME_OFF));
331
332
333
334
335
336
337
338
	}
	speakup_date(vc);
}

static void speakup_parked(struct vc_data *vc)
{
	if (spk_parked & 0x80) {
		spk_parked = 0;
339
		synth_printf("%s\n", spk_msg_get(MSG_UNPARKED));
340
341
	} else {
		spk_parked |= 0x80;
342
		synth_printf("%s\n", spk_msg_get(MSG_PARKED));
343
344
345
346
347
348
349
350
351
352
	}
}

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;
353
354
		spk_xs = (u_short) spk_x;
		spk_ys = (u_short) spk_y;
355
		spk_sel_cons = vc;
356
		synth_printf("%s\n", spk_msg_get(MSG_MARK));
357
358
		return;
	}
359
360
	spk_xe = (u_short) spk_x;
	spk_ye = (u_short) spk_y;
361
	mark_cut_flag = 0;
362
	synth_printf("%s\n", spk_msg_get(MSG_CUT));
363
364
365
366
367
368

	speakup_clear_selection();
	ret = speakup_set_selection(tty);

	switch (ret) {
	case 0:
369
370
		break;		/* no error */
	case -EFAULT:
371
372
		pr_warn("%sEFAULT\n", err_buf);
		break;
373
	case -EINVAL:
374
375
		pr_warn("%sEINVAL\n", err_buf);
		break;
376
	case -ENOMEM:
377
378
379
380
381
382
383
384
385
		pr_warn("%sENOMEM\n", err_buf);
		break;
	}
}

static void speakup_paste(struct vc_data *vc)
{
	if (mark_cut_flag) {
		mark_cut_flag = 0;
386
		synth_printf("%s\n", spk_msg_get(MSG_MARK_CLEARED));
387
	} else {
388
		synth_printf("%s\n", spk_msg_get(MSG_PASTE));
389
390
391
392
393
394
395
396
397
		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) {
398
		synth_printf("%s ", spk_msg_get(MSG_BRIGHT));
399
400
		fg -= 8;
	}
401
	synth_printf("%s", spk_msg_get(MSG_COLORS_START + fg));
402
	if (bg > 7) {
403
		synth_printf(" %s ", spk_msg_get(MSG_ON_BLINKING));
404
405
		bg -= 8;
	} else
406
407
		synth_printf(" %s ", spk_msg_get(MSG_ON));
	synth_printf("%s\n", spk_msg_get(MSG_COLORS_START + bg));
408
409
410
411
412
413
414
415
416
417
418
419
}

enum {
	edge_top = 1,
	edge_bottom,
	edge_left,
	edge_right,
	edge_quiet
};

static void announce_edge(struct vc_data *vc, int msg_id)
{
420
	if (spk_bleeps & 1)
421
		bleep(spk_y);
422
423
	if ((spk_bleeps & 2) && (msg_id < edge_quiet))
		synth_printf("%s\n", spk_msg_get(MSG_EDGE_MSGS_START + msg_id - 1));
424
425
426
427
}

static void speak_char(u_char ch)
{
428
429
	char *cp = spk_characters[ch];
	struct var_t *direct = spk_get_var(DIRECT);
430
431
	if (direct && direct->u.n.value) {
		if (IS_CHAR(ch, B_CAP)) {
432
433
			spk_pitch_shift++;
			synth_printf("%s", spk_str_caps_start);
434
435
436
		}
		synth_printf("%c", ch);
		if (IS_CHAR(ch, B_CAP))
437
			synth_printf("%s", spk_str_caps_stop);
438
439
440
441
442
443
444
445
		return;
	}
	if (cp == NULL) {
		pr_info("speak_char: cp == NULL!\n");
		return;
	}
	synth_buffer_add(SPACE);
	if (IS_CHAR(ch, B_CAP)) {
446
447
		spk_pitch_shift++;
		synth_printf("%s", spk_str_caps_start);
448
		synth_printf("%s", cp);
449
		synth_printf("%s", spk_str_caps_stop);
450
451
	} else {
		if (*cp == '^') {
452
			synth_printf("%s", spk_msg_get(MSG_CTRL));
453
454
455
456
457
458
459
			cp++;
		}
		synth_printf("%s", cp);
	}
	synth_buffer_add(SPACE);
}

460
static u16 get_char(struct vc_data *vc, u16 *pos, u_char *attribs)
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
{
	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) {
482
		if (spk_attrib_bleep & 1)
483
			bleep(spk_y);
484
		if (spk_attrib_bleep & 2)
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
			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))
500
			synth_printf("%s ", spk_msg_get(MSG_NUMBER));
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
		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
530
531
 * reading cursor is a space and if spk_say_word_ctl is true it will
 * return the word space.  If spk_say_word_ctl is not set it will check to
532
533
534
535
 * 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.. */
536
537
538
539
540
541
542
543

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;
544
	ch = (char)get_char(vc, (u_short *) tmp_pos, &temp);
545
546

/* decided to take out the sayword if on a space (mis-information */
547
	if (spk_say_word_ctl && ch == SPACE) {
548
		*buf = '\0';
549
		synth_printf("%s\n", spk_msg_get(MSG_SPACE));
550
551
552
		return 0;
	} else if ((tmpx < vc->vc_cols - 2)
		   && (ch == SPACE || ch == 0 || IS_WDLM(ch))
553
554
		   && ((char)get_char(vc, (u_short *) &tmp_pos + 1, &temp) >
		       SPACE)) {
555
556
557
558
		tmp_pos += 2;
		tmpx++;
	} else
		while (tmpx > 0) {
559
			ch = (char)get_char(vc, (u_short *) tmp_pos - 1, &temp);
560
			if ((ch == SPACE || ch == 0 || IS_WDLM(ch))
561
562
			    && ((char)get_char(vc, (u_short *) tmp_pos, &temp) >
				SPACE))
563
564
565
566
567
568
569
570
571
				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++;
572
573
574
		ch = (char)get_char(vc, (u_short *) tmp_pos, &temp);
		if ((ch == SPACE) || ch == 0
		    || (IS_WDLM(buf[cnt - 1]) && (ch > SPACE)))
575
576
577
578
579
580
581
582
583
584
			break;
		buf[cnt++] = ch;
	}
	buf[cnt] = '\0';
	return cnt;
}

static void say_word(struct vc_data *vc)
{
	u_long cnt = get_word(vc);
585
	u_short saved_punc_mask = spk_punc_mask;
586
587
	if (cnt == 0)
		return;
588
	spk_punc_mask = PUNC;
589
590
	buf[cnt++] = SPACE;
	spkup_write(buf, cnt);
591
	spk_punc_mask = saved_punc_mask;
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
}

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--;
624
625
		spk_pos -= 2;
		ch = (char)get_char(vc, (u_short *) spk_pos, &temp);
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
		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) {
658
		ch = (char)get_char(vc, (u_short *) spk_pos, &temp);
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
		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[] = { "", ",", ".", ". .", ". . ." };
689
690
	char *cp = buf, *str_cap = spk_str_caps_stop;
	char *cp1, *last_cap = spk_str_caps_stop;
691
692
693
694
695
	u_char ch;
	if (!get_word(vc))
		return;
	while ((ch = (u_char) *cp)) {
		if (cp != buf)
696
			synth_printf(" %s ", delay_str[spk_spell_delay]);
697
		if (IS_CHAR(ch, B_CAP)) {
698
699
700
			str_cap = spk_str_caps_start;
			if (*spk_str_caps_stop)
				spk_pitch_shift++;
701
			else	/* synth has no pitch */
702
				last_cap = spk_str_caps_stop;
703
		} else
704
			str_cap = spk_str_caps_stop;
705
706
707
708
709
710
711
712
713
		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 {
714
			cp1 = spk_characters[ch];
715
			if (*cp1 == '^') {
716
				synth_printf("%s", spk_msg_get(MSG_CTRL));
717
718
719
720
721
722
				cp1++;
			}
		}
		synth_printf("%s", cp1);
		cp++;
	}
723
724
	if (str_cap != spk_str_caps_stop)
		synth_printf("%s", spk_str_caps_stop);
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
}

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;
749
	u_short saved_punc_mask = spk_punc_mask;
750
	if (i == 0) {
751
		synth_printf("%s\n", spk_msg_get(MSG_BLANK));
752
753
754
755
		return;
	}
	buf[i++] = '\n';
	if (this_speakup_key == SAY_LINE_INDENT) {
756
757
758
		cp = buf;
		while (*cp == SPACE)
			cp++;
759
760
		synth_printf("%d, ", (cp - buf) + 1);
	}
761
	spk_punc_mask = spk_punc_masks[spk_reading_punc];
762
	spkup_write(buf, i);
763
	spk_punc_mask = saved_punc_mask;
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
}

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;
795
	u_short saved_punc_mask = spk_punc_mask;
796
797
798
	spk_old_attr = spk_attr;
	spk_attr = get_attributes((u_short *) from);
	while (from < to) {
799
		buf[i++] = (char)get_char(vc, (u_short *) from, &tmp);
800
801
802
803
804
805
806
807
808
809
810
811
		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)
812
		spk_punc_mask = spk_punc_info[spk_reading_punc].mask;
813
814
	spkup_write(buf, i);
	if (read_punc)
815
		spk_punc_mask = saved_punc_mask;
816
817
818
819
820
821
822
823
824
825
826
	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)
827
			synth_printf("%s\n", spk_msg_get(MSG_BLANK));
828
829
830
831
832
833
834
835
836
837
838
839
}

/* Sentence Reading Commands */

static int currsentence;
static int numsentences[2];
static char *sentbufend[2];
static char *sentmarks[2][10];
static int currbuf;
static int bn;
static char sentbuf[2][256];

840
static int say_sentence_num(int num, int prev)
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
{
	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);
865
	end = vc->vc_origin + ((spk_y) * vc->vc_size_row) + vc->vc_cols * 2;
866
867
868
869
870
871
872
873

	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) {
874
		sentbuf[bn][i] = (char)get_char(vc, (u_short *) start, &tmp);
875
		if (i > 0) {
876
			if (sentbuf[bn][i] == SPACE && sentbuf[bn][i - 1] == '.'
877
878
879
880
			    && numsentences[bn] < 9) {
				/* Sentence Marker */
				numsentences[bn]++;
				sentmarks[bn][numsentences[bn]] =
881
				    &sentbuf[bn][i];
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
			}
		}
		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) {
927
		synth_printf("%s\n", spk_msg_get(MSG_NO_WINDOW));
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
		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) {
978
		synth_printf("%s\n", spk_msg_get(MSG_BLANK));
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
		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) {
997
		synth_printf("%s\n", spk_msg_get(MSG_BLANK));
998
999
1000
		return;
	}
	ch = buf[--len];