| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  * linux/drivers/char/selection.c | 
 | 3 |  * | 
 | 4 |  * This module exports the functions: | 
 | 5 |  * | 
 | 6 |  *     'int set_selection(struct tiocl_selection __user *, struct tty_struct *)' | 
 | 7 |  *     'void clear_selection(void)' | 
 | 8 |  *     'int paste_selection(struct tty_struct *)' | 
 | 9 |  *     'int sel_loadlut(char __user *)' | 
 | 10 |  * | 
 | 11 |  * Now that /dev/vcs exists, most of this can disappear again. | 
 | 12 |  */ | 
 | 13 |  | 
 | 14 | #include <linux/module.h> | 
 | 15 | #include <linux/tty.h> | 
 | 16 | #include <linux/sched.h> | 
 | 17 | #include <linux/mm.h> | 
 | 18 | #include <linux/slab.h> | 
 | 19 | #include <linux/types.h> | 
 | 20 |  | 
 | 21 | #include <asm/uaccess.h> | 
 | 22 |  | 
| Jan Engelhardt | 759448f | 2007-07-15 23:40:40 -0700 | [diff] [blame] | 23 | #include <linux/kbd_kern.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 24 | #include <linux/vt_kern.h> | 
 | 25 | #include <linux/consolemap.h> | 
 | 26 | #include <linux/selection.h> | 
 | 27 | #include <linux/tiocl.h> | 
 | 28 | #include <linux/console.h> | 
 | 29 |  | 
 | 30 | /* Don't take this from <ctype.h>: 011-015 on the screen aren't spaces */ | 
 | 31 | #define isspace(c)	((c) == ' ') | 
 | 32 |  | 
 | 33 | extern void poke_blanked_console(void); | 
 | 34 |  | 
 | 35 | /* Variables for selection control. */ | 
 | 36 | /* Use a dynamic buffer, instead of static (Dec 1994) */ | 
| Alan Cox | ca9bda0 | 2006-09-29 02:00:03 -0700 | [diff] [blame] | 37 | struct vc_data *sel_cons;		/* must not be deallocated */ | 
| Jan Engelhardt | 759448f | 2007-07-15 23:40:40 -0700 | [diff] [blame] | 38 | static int use_unicode; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 39 | static volatile int sel_start = -1; 	/* cleared by clear_selection */ | 
 | 40 | static int sel_end; | 
 | 41 | static int sel_buffer_lth; | 
 | 42 | static char *sel_buffer; | 
 | 43 |  | 
 | 44 | /* clear_selection, highlight and highlight_pointer can be called | 
 | 45 |    from interrupt (via scrollback/front) */ | 
 | 46 |  | 
 | 47 | /* set reverse video on characters s-e of console with selection. */ | 
 | 48 | static inline void highlight(const int s, const int e) | 
 | 49 | { | 
 | 50 | 	invert_screen(sel_cons, s, e-s+2, 1); | 
 | 51 | } | 
 | 52 |  | 
 | 53 | /* use complementary color to show the pointer */ | 
 | 54 | static inline void highlight_pointer(const int where) | 
 | 55 | { | 
 | 56 | 	complement_pos(sel_cons, where); | 
 | 57 | } | 
 | 58 |  | 
| Jan Engelhardt | 759448f | 2007-07-15 23:40:40 -0700 | [diff] [blame] | 59 | static u16 | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 60 | sel_pos(int n) | 
 | 61 | { | 
| Jan Engelhardt | 759448f | 2007-07-15 23:40:40 -0700 | [diff] [blame] | 62 | 	return inverse_translate(sel_cons, screen_glyph(sel_cons, n), | 
 | 63 | 				use_unicode); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 64 | } | 
 | 65 |  | 
 | 66 | /* remove the current selection highlight, if any, | 
 | 67 |    from the console holding the selection. */ | 
 | 68 | void | 
 | 69 | clear_selection(void) { | 
 | 70 | 	highlight_pointer(-1); /* hide the pointer */ | 
 | 71 | 	if (sel_start != -1) { | 
 | 72 | 		highlight(sel_start, sel_end); | 
 | 73 | 		sel_start = -1; | 
 | 74 | 	} | 
 | 75 | } | 
 | 76 |  | 
 | 77 | /* | 
 | 78 |  * User settable table: what characters are to be considered alphabetic? | 
 | 79 |  * 256 bits | 
 | 80 |  */ | 
 | 81 | static u32 inwordLut[8]={ | 
 | 82 |   0x00000000, /* control chars     */ | 
 | 83 |   0x03FF0000, /* digits            */ | 
 | 84 |   0x87FFFFFE, /* uppercase and '_' */ | 
 | 85 |   0x07FFFFFE, /* lowercase         */ | 
 | 86 |   0x00000000, | 
 | 87 |   0x00000000, | 
 | 88 |   0xFF7FFFFF, /* latin-1 accented letters, not multiplication sign */ | 
 | 89 |   0xFF7FFFFF  /* latin-1 accented letters, not division sign */ | 
 | 90 | }; | 
 | 91 |  | 
| Jan Engelhardt | 759448f | 2007-07-15 23:40:40 -0700 | [diff] [blame] | 92 | static inline int inword(const u16 c) { | 
 | 93 | 	return c > 0xff || (( inwordLut[c>>5] >> (c & 0x1F) ) & 1); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 94 | } | 
 | 95 |  | 
 | 96 | /* set inwordLut contents. Invoked by ioctl(). */ | 
 | 97 | int sel_loadlut(char __user *p) | 
 | 98 | { | 
 | 99 | 	return copy_from_user(inwordLut, (u32 __user *)(p+4), 32) ? -EFAULT : 0; | 
 | 100 | } | 
 | 101 |  | 
 | 102 | /* does screen address p correspond to character at LH/RH edge of screen? */ | 
 | 103 | static inline int atedge(const int p, int size_row) | 
 | 104 | { | 
 | 105 | 	return (!(p % size_row)	|| !((p + 2) % size_row)); | 
 | 106 | } | 
 | 107 |  | 
 | 108 | /* constrain v such that v <= u */ | 
 | 109 | static inline unsigned short limit(const unsigned short v, const unsigned short u) | 
 | 110 | { | 
 | 111 | 	return (v > u) ? u : v; | 
 | 112 | } | 
 | 113 |  | 
| Jan Engelhardt | 759448f | 2007-07-15 23:40:40 -0700 | [diff] [blame] | 114 | /* stores the char in UTF8 and returns the number of bytes used (1-3) */ | 
 | 115 | static int store_utf8(u16 c, char *p) | 
 | 116 | { | 
 | 117 | 	if (c < 0x80) { | 
 | 118 | 		/*  0******* */ | 
 | 119 | 		p[0] = c; | 
 | 120 | 		return 1; | 
 | 121 | 	} else if (c < 0x800) { | 
 | 122 | 		/* 110***** 10****** */ | 
 | 123 | 		p[0] = 0xc0 | (c >> 6); | 
 | 124 | 		p[1] = 0x80 | (c & 0x3f); | 
 | 125 | 		return 2; | 
 | 126 |     	} else { | 
 | 127 | 		/* 1110**** 10****** 10****** */ | 
 | 128 | 		p[0] = 0xe0 | (c >> 12); | 
 | 129 | 		p[1] = 0x80 | ((c >> 6) & 0x3f); | 
 | 130 | 		p[2] = 0x80 | (c & 0x3f); | 
 | 131 | 		return 3; | 
 | 132 |     	} | 
 | 133 | } | 
 | 134 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 135 | /* set the current selection. Invoked by ioctl() or by kernel code. */ | 
 | 136 | int set_selection(const struct tiocl_selection __user *sel, struct tty_struct *tty) | 
 | 137 | { | 
 | 138 | 	struct vc_data *vc = vc_cons[fg_console].d; | 
 | 139 | 	int sel_mode, new_sel_start, new_sel_end, spc; | 
 | 140 | 	char *bp, *obp; | 
| Jan Engelhardt | 759448f | 2007-07-15 23:40:40 -0700 | [diff] [blame] | 141 | 	int i, ps, pe, multiplier; | 
 | 142 | 	u16 c; | 
 | 143 | 	struct kbd_struct *kbd = kbd_table + fg_console; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 144 |  | 
 | 145 | 	poke_blanked_console(); | 
 | 146 |  | 
 | 147 | 	{ unsigned short xs, ys, xe, ye; | 
 | 148 |  | 
 | 149 | 	  if (!access_ok(VERIFY_READ, sel, sizeof(*sel))) | 
 | 150 | 		return -EFAULT; | 
 | 151 | 	  __get_user(xs, &sel->xs); | 
 | 152 | 	  __get_user(ys, &sel->ys); | 
 | 153 | 	  __get_user(xe, &sel->xe); | 
 | 154 | 	  __get_user(ye, &sel->ye); | 
 | 155 | 	  __get_user(sel_mode, &sel->sel_mode); | 
 | 156 | 	  xs--; ys--; xe--; ye--; | 
 | 157 | 	  xs = limit(xs, vc->vc_cols - 1); | 
 | 158 | 	  ys = limit(ys, vc->vc_rows - 1); | 
 | 159 | 	  xe = limit(xe, vc->vc_cols - 1); | 
 | 160 | 	  ye = limit(ye, vc->vc_rows - 1); | 
 | 161 | 	  ps = ys * vc->vc_size_row + (xs << 1); | 
 | 162 | 	  pe = ye * vc->vc_size_row + (xe << 1); | 
 | 163 |  | 
 | 164 | 	  if (sel_mode == TIOCL_SELCLEAR) { | 
 | 165 | 	      /* useful for screendump without selection highlights */ | 
 | 166 | 	      clear_selection(); | 
 | 167 | 	      return 0; | 
 | 168 | 	  } | 
 | 169 |  | 
 | 170 | 	  if (mouse_reporting() && (sel_mode & TIOCL_SELMOUSEREPORT)) { | 
 | 171 | 	      mouse_report(tty, sel_mode & TIOCL_SELBUTTONMASK, xs, ys); | 
 | 172 | 	      return 0; | 
 | 173 | 	  } | 
 | 174 |         } | 
 | 175 |  | 
 | 176 | 	if (ps > pe)	/* make sel_start <= sel_end */ | 
 | 177 | 	{ | 
 | 178 | 		int tmp = ps; | 
 | 179 | 		ps = pe; | 
 | 180 | 		pe = tmp; | 
 | 181 | 	} | 
 | 182 |  | 
 | 183 | 	if (sel_cons != vc_cons[fg_console].d) { | 
 | 184 | 		clear_selection(); | 
 | 185 | 		sel_cons = vc_cons[fg_console].d; | 
 | 186 | 	} | 
| Jan Engelhardt | 759448f | 2007-07-15 23:40:40 -0700 | [diff] [blame] | 187 | 	use_unicode = kbd && kbd->kbdmode == VC_UNICODE; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 188 |  | 
 | 189 | 	switch (sel_mode) | 
 | 190 | 	{ | 
 | 191 | 		case TIOCL_SELCHAR:	/* character-by-character selection */ | 
 | 192 | 			new_sel_start = ps; | 
 | 193 | 			new_sel_end = pe; | 
 | 194 | 			break; | 
 | 195 | 		case TIOCL_SELWORD:	/* word-by-word selection */ | 
 | 196 | 			spc = isspace(sel_pos(ps)); | 
 | 197 | 			for (new_sel_start = ps; ; ps -= 2) | 
 | 198 | 			{ | 
 | 199 | 				if ((spc && !isspace(sel_pos(ps))) || | 
 | 200 | 				    (!spc && !inword(sel_pos(ps)))) | 
 | 201 | 					break; | 
 | 202 | 				new_sel_start = ps; | 
 | 203 | 				if (!(ps % vc->vc_size_row)) | 
 | 204 | 					break; | 
 | 205 | 			} | 
 | 206 | 			spc = isspace(sel_pos(pe)); | 
 | 207 | 			for (new_sel_end = pe; ; pe += 2) | 
 | 208 | 			{ | 
 | 209 | 				if ((spc && !isspace(sel_pos(pe))) || | 
 | 210 | 				    (!spc && !inword(sel_pos(pe)))) | 
 | 211 | 					break; | 
 | 212 | 				new_sel_end = pe; | 
 | 213 | 				if (!((pe + 2) % vc->vc_size_row)) | 
 | 214 | 					break; | 
 | 215 | 			} | 
 | 216 | 			break; | 
 | 217 | 		case TIOCL_SELLINE:	/* line-by-line selection */ | 
 | 218 | 			new_sel_start = ps - ps % vc->vc_size_row; | 
 | 219 | 			new_sel_end = pe + vc->vc_size_row | 
 | 220 | 				    - pe % vc->vc_size_row - 2; | 
 | 221 | 			break; | 
 | 222 | 		case TIOCL_SELPOINTER: | 
 | 223 | 			highlight_pointer(pe); | 
 | 224 | 			return 0; | 
 | 225 | 		default: | 
 | 226 | 			return -EINVAL; | 
 | 227 | 	} | 
 | 228 |  | 
 | 229 | 	/* remove the pointer */ | 
 | 230 | 	highlight_pointer(-1); | 
 | 231 |  | 
 | 232 | 	/* select to end of line if on trailing space */ | 
 | 233 | 	if (new_sel_end > new_sel_start && | 
 | 234 | 		!atedge(new_sel_end, vc->vc_size_row) && | 
 | 235 | 		isspace(sel_pos(new_sel_end))) { | 
 | 236 | 		for (pe = new_sel_end + 2; ; pe += 2) | 
 | 237 | 			if (!isspace(sel_pos(pe)) || | 
 | 238 | 			    atedge(pe, vc->vc_size_row)) | 
 | 239 | 				break; | 
 | 240 | 		if (isspace(sel_pos(pe))) | 
 | 241 | 			new_sel_end = pe; | 
 | 242 | 	} | 
 | 243 | 	if (sel_start == -1)	/* no current selection */ | 
 | 244 | 		highlight(new_sel_start, new_sel_end); | 
 | 245 | 	else if (new_sel_start == sel_start) | 
 | 246 | 	{ | 
 | 247 | 		if (new_sel_end == sel_end)	/* no action required */ | 
 | 248 | 			return 0; | 
 | 249 | 		else if (new_sel_end > sel_end)	/* extend to right */ | 
 | 250 | 			highlight(sel_end + 2, new_sel_end); | 
 | 251 | 		else				/* contract from right */ | 
 | 252 | 			highlight(new_sel_end + 2, sel_end); | 
 | 253 | 	} | 
 | 254 | 	else if (new_sel_end == sel_end) | 
 | 255 | 	{ | 
 | 256 | 		if (new_sel_start < sel_start)	/* extend to left */ | 
 | 257 | 			highlight(new_sel_start, sel_start - 2); | 
 | 258 | 		else				/* contract from left */ | 
 | 259 | 			highlight(sel_start, new_sel_start - 2); | 
 | 260 | 	} | 
 | 261 | 	else	/* some other case; start selection from scratch */ | 
 | 262 | 	{ | 
 | 263 | 		clear_selection(); | 
 | 264 | 		highlight(new_sel_start, new_sel_end); | 
 | 265 | 	} | 
 | 266 | 	sel_start = new_sel_start; | 
 | 267 | 	sel_end = new_sel_end; | 
 | 268 |  | 
 | 269 | 	/* Allocate a new buffer before freeing the old one ... */ | 
| Jan Engelhardt | 759448f | 2007-07-15 23:40:40 -0700 | [diff] [blame] | 270 | 	multiplier = use_unicode ? 3 : 1;  /* chars can take up to 3 bytes */ | 
| Mikulas Patocka | 878b861 | 2009-01-30 15:27:14 -0500 | [diff] [blame] | 271 | 	bp = kmalloc(((sel_end-sel_start)/2+1)*multiplier, GFP_KERNEL); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 272 | 	if (!bp) { | 
 | 273 | 		printk(KERN_WARNING "selection: kmalloc() failed\n"); | 
 | 274 | 		clear_selection(); | 
 | 275 | 		return -ENOMEM; | 
 | 276 | 	} | 
| Jesper Juhl | 735d566 | 2005-11-07 01:01:29 -0800 | [diff] [blame] | 277 | 	kfree(sel_buffer); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 278 | 	sel_buffer = bp; | 
 | 279 |  | 
 | 280 | 	obp = bp; | 
 | 281 | 	for (i = sel_start; i <= sel_end; i += 2) { | 
| Jan Engelhardt | 759448f | 2007-07-15 23:40:40 -0700 | [diff] [blame] | 282 | 		c = sel_pos(i); | 
 | 283 | 		if (use_unicode) | 
 | 284 | 			bp += store_utf8(c, bp); | 
 | 285 | 		else | 
 | 286 | 			*bp++ = c; | 
 | 287 | 		if (!isspace(c)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 288 | 			obp = bp; | 
 | 289 | 		if (! ((i + 2) % vc->vc_size_row)) { | 
 | 290 | 			/* strip trailing blanks from line and add newline, | 
 | 291 | 			   unless non-space at end of line. */ | 
 | 292 | 			if (obp != bp) { | 
 | 293 | 				bp = obp; | 
 | 294 | 				*bp++ = '\r'; | 
 | 295 | 			} | 
 | 296 | 			obp = bp; | 
 | 297 | 		} | 
 | 298 | 	} | 
 | 299 | 	sel_buffer_lth = bp - sel_buffer; | 
 | 300 | 	return 0; | 
 | 301 | } | 
 | 302 |  | 
 | 303 | /* Insert the contents of the selection buffer into the | 
 | 304 |  * queue of the tty associated with the current console. | 
 | 305 |  * Invoked by ioctl(). | 
 | 306 |  */ | 
 | 307 | int paste_selection(struct tty_struct *tty) | 
 | 308 | { | 
| Alan Cox | c9f19e9 | 2009-01-02 13:47:26 +0000 | [diff] [blame] | 309 | 	struct vc_data *vc = tty->driver_data; | 
| Alan Cox | 33f0f88 | 2006-01-09 20:54:13 -0800 | [diff] [blame] | 310 | 	int	pasted = 0; | 
 | 311 | 	unsigned int count; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 312 | 	struct  tty_ldisc *ld; | 
 | 313 | 	DECLARE_WAITQUEUE(wait, current); | 
 | 314 |  | 
 | 315 | 	acquire_console_sem(); | 
 | 316 | 	poke_blanked_console(); | 
 | 317 | 	release_console_sem(); | 
 | 318 |  | 
 | 319 | 	ld = tty_ldisc_ref_wait(tty); | 
 | 320 | 	 | 
 | 321 | 	add_wait_queue(&vc->paste_wait, &wait); | 
 | 322 | 	while (sel_buffer && sel_buffer_lth > pasted) { | 
 | 323 | 		set_current_state(TASK_INTERRUPTIBLE); | 
 | 324 | 		if (test_bit(TTY_THROTTLED, &tty->flags)) { | 
 | 325 | 			schedule(); | 
 | 326 | 			continue; | 
 | 327 | 		} | 
 | 328 | 		count = sel_buffer_lth - pasted; | 
| Alan Cox | 33f0f88 | 2006-01-09 20:54:13 -0800 | [diff] [blame] | 329 | 		count = min(count, tty->receive_room); | 
| Alan Cox | c65c9bc | 2009-06-11 12:50:12 +0100 | [diff] [blame] | 330 | 		tty->ldisc->ops->receive_buf(tty, sel_buffer + pasted, | 
| Alan Cox | a352def | 2008-07-16 21:53:12 +0100 | [diff] [blame] | 331 | 								NULL, count); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 332 | 		pasted += count; | 
 | 333 | 	} | 
 | 334 | 	remove_wait_queue(&vc->paste_wait, &wait); | 
| Milind Arun Choudhary | cc0a8fb | 2007-05-08 00:30:52 -0700 | [diff] [blame] | 335 | 	__set_current_state(TASK_RUNNING); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 336 |  | 
 | 337 | 	tty_ldisc_deref(ld); | 
 | 338 | 	return 0; | 
 | 339 | } |