190 lines · 4.6 KB · C
RawDownload
#include <linux/slab.h> /* for kmalloc */#include <linux/consolemap.h>#include <linux/interrupt.h>#include <linux/sched.h>#include <linux/device.h> /* for dev_warn */#include <linux/selection.h>#include <linux/workqueue.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/atomic.h> #include "speakup.h" /* ------ cut and paste ----- *//* Don't take this from <ctype.h>: 011-015 on the screen aren't spaces */#define ishardspace(c)      ((c) == ' ') unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */ /* Variables for selection control. *//* must not be deallocated */struct vc_data *spk_sel_cons;/* cleared by clear_selection */static int sel_start = -1;static int sel_end;static int sel_buffer_lth;static char *sel_buffer; static unsigned char sel_pos(int n){	return inverse_translate(spk_sel_cons,		screen_glyph(spk_sel_cons, n), 0);} void speakup_clear_selection(void){	sel_start = -1;} /* does screen address p correspond to character at LH/RH edge of screen? */static int atedge(const int p, int size_row){	return !(p % size_row) || !((p + 2) % size_row);} /* constrain v such that v <= u */static unsigned short limit(const unsigned short v, const unsigned short u){	return (v > u) ? u : v;} int speakup_set_selection(struct tty_struct *tty){	int new_sel_start, new_sel_end;	char *bp, *obp;	int i, ps, pe;	struct vc_data *vc = vc_cons[fg_console].d; 	spk_xs = limit(spk_xs, vc->vc_cols - 1);	spk_ys = limit(spk_ys, vc->vc_rows - 1);	spk_xe = limit(spk_xe, vc->vc_cols - 1);	spk_ye = limit(spk_ye, vc->vc_rows - 1);	ps = spk_ys * vc->vc_size_row + (spk_xs << 1);	pe = spk_ye * vc->vc_size_row + (spk_xe << 1); 	if (ps > pe) {		/* make sel_start <= sel_end */		int tmp = ps; 		ps = pe;		pe = tmp;	} 	if (spk_sel_cons != vc_cons[fg_console].d) {		speakup_clear_selection();		spk_sel_cons = vc_cons[fg_console].d;		dev_warn(tty->dev,			 "Selection: mark console not the same as cut\n");		return -EINVAL;	} 	new_sel_start = ps;	new_sel_end = pe; 	/* select to end of line if on trailing space */	if (new_sel_end > new_sel_start &&	    !atedge(new_sel_end, vc->vc_size_row) &&	    ishardspace(sel_pos(new_sel_end))) {		for (pe = new_sel_end + 2; ; pe += 2)			if (!ishardspace(sel_pos(pe)) ||			    atedge(pe, vc->vc_size_row))				break;		if (ishardspace(sel_pos(pe)))			new_sel_end = pe;	}	if ((new_sel_start == sel_start) && (new_sel_end == sel_end))		return 0; /* no action required */ 	sel_start = new_sel_start;	sel_end = new_sel_end;	/* Allocate a new buffer before freeing the old one ... */	bp = kmalloc((sel_end - sel_start) / 2 + 1, GFP_ATOMIC);	if (!bp) {		speakup_clear_selection();		return -ENOMEM;	}	kfree(sel_buffer);	sel_buffer = bp; 	obp = bp;	for (i = sel_start; i <= sel_end; i += 2) {		*bp = sel_pos(i);		if (!ishardspace(*bp++))			obp = bp;		if (!((i + 2) % vc->vc_size_row)) {			/* strip trailing blanks from line and add newline,			 * unless non-space at end of line.			 */			if (obp != bp) {				bp = obp;				*bp++ = '\r';			}			obp = bp;		}	}	sel_buffer_lth = bp - sel_buffer;	return 0;} struct speakup_paste_work {	struct work_struct work;	struct tty_struct *tty;}; static void __speakup_paste_selection(struct work_struct *work){	struct speakup_paste_work *spw =		container_of(work, struct speakup_paste_work, work);	struct tty_struct *tty = xchg(&spw->tty, NULL);	struct vc_data *vc = (struct vc_data *)tty->driver_data;	int pasted = 0, count;	struct tty_ldisc *ld;	DECLARE_WAITQUEUE(wait, current); 	ld = tty_ldisc_ref(tty);	if (!ld)		goto tty_unref;	tty_buffer_lock_exclusive(&vc->port); 	add_wait_queue(&vc->paste_wait, &wait);	while (sel_buffer && sel_buffer_lth > pasted) {		set_current_state(TASK_INTERRUPTIBLE);		if (tty_throttled(tty)) {			schedule();			continue;		}		count = sel_buffer_lth - pasted;		count = tty_ldisc_receive_buf(ld, sel_buffer + pasted, NULL,					      count);		pasted += count;	}	remove_wait_queue(&vc->paste_wait, &wait);	__set_current_state(TASK_RUNNING); 	tty_buffer_unlock_exclusive(&vc->port);	tty_ldisc_deref(ld);tty_unref:	tty_kref_put(tty);} static struct speakup_paste_work speakup_paste_work = {	.work = __WORK_INITIALIZER(speakup_paste_work.work,				   __speakup_paste_selection)}; int speakup_paste_selection(struct tty_struct *tty){	if (cmpxchg(&speakup_paste_work.tty, NULL, tty))		return -EBUSY; 	tty_kref_get(tty);	schedule_work_on(WORK_CPU_UNBOUND, &speakup_paste_work.work);	return 0;} void speakup_cancel_paste(void){	cancel_work_sync(&speakup_paste_work.work);	tty_kref_put(speakup_paste_work.tty);}