/*
       * linux/drivers/char/selection.c
       *
       * This module exports the functions:
       *
       *     'int set_selection(const unsigned long arg)'
       *     'void clear_selection(void)'
       *     'int paste_selection(struct tty_struct *tty)'
       *     'int sel_loadlut(const unsigned long arg)'
       *
       * Now that /dev/vcs exists, most of this can disappear again.
       */
      
      #include <linux/module.h>
      #include <linux/tty.h>
      #include <linux/sched.h>
      #include <linux/mm.h>
      #include <linux/malloc.h>
      #include <linux/types.h>
      
      #include <asm/uaccess.h>
      
      #include <linux/vt_kern.h>
      #include <linux/consolemap.h>
      #include <linux/console_struct.h>
      #include <linux/selection.h>
      
      #ifndef MIN
      #define MIN(a,b)	((a) < (b) ? (a) : (b))
      #endif
      
      /* Don't take this from <ctype.h>: 011-015 on the screen aren't spaces */
      #define isspace(c)	((c) == ' ')
      
      extern void poke_blanked_console(void);
      
      /* Variables for selection control. */
      /* Use a dynamic buffer, instead of static (Dec 1994) */
             int sel_cons;		/* must not be disallocated */
      static volatile int sel_start = -1; 	/* cleared by clear_selection */
      static int sel_end;
      static int sel_buffer_lth;
      static char *sel_buffer;
      
      /* clear_selection, highlight and highlight_pointer can be called
         from interrupt (via scrollback/front) */
      
      /* set reverse video on characters s-e of console with selection. */
      inline static void
  50  highlight(const int s, const int e) {
      	invert_screen(sel_cons, s, e-s+2, 1);
      }
      
      /* use complementary color to show the pointer */
      inline static void
  56  highlight_pointer(const int where) {
      	complement_pos(sel_cons, where);
      }
      
      static unsigned char
  61  sel_pos(int n)
      {
  63  	return inverse_translate(vc_cons[sel_cons].d, screen_glyph(sel_cons, n));
      }
      
      /* remove the current selection highlight, if any,
         from the console holding the selection. */
      void
  69  clear_selection(void) {
      	highlight_pointer(-1); /* hide the pointer */
  71  	if (sel_start != -1) {
      		highlight(sel_start, sel_end);
      		sel_start = -1;
      	}
      }
      
      /*
       * User settable table: what characters are to be considered alphabetic?
       * 256 bits
       */
      static u32 inwordLut[8]={
        0x00000000, /* control chars     */
        0x03FF0000, /* digits            */
        0x87FFFFFE, /* uppercase and '_' */
        0x07FFFFFE, /* lowercase         */
        0x00000000,
        0x00000000,
        0xFF7FFFFF, /* latin-1 accented letters, not multiplication sign */
        0xFF7FFFFF  /* latin-1 accented letters, not division sign */
      };
      
  92  static inline int inword(const unsigned char c) {
  93  	return ( inwordLut[c>>5] >> (c & 0x1F) ) & 1;
      }
      
      /* set inwordLut contents. Invoked by ioctl(). */
  97  int sel_loadlut(const unsigned long arg)
      {
      	int err = -EFAULT;
      
 101  	if (!copy_from_user(inwordLut, (u32 *)(arg+4), 32))
      		err = 0;
 103  	return err;
      }
      
      /* does screen address p correspond to character at LH/RH edge of screen? */
 107  static inline int atedge(const int p, int size_row)
      {
 109  	return (!(p % size_row)	|| !((p + 2) % size_row));
      }
      
      /* constrain v such that v <= u */
 113  static inline unsigned short limit(const unsigned short v, const unsigned short u)
      {
 115  	return (v > u) ? u : v;
      }
      
      /* set the current selection. Invoked by ioctl() or by kernel code. */
 119  int set_selection(const unsigned long arg, struct tty_struct *tty, int user)
      {
      	int sel_mode, new_sel_start, new_sel_end, spc;
      	char *bp, *obp;
      	int i, ps, pe;
      	unsigned int currcons = fg_console;
      
      	unblank_screen();
      	poke_blanked_console();
      
      	{ unsigned short *args, xs, ys, xe, ye;
      
      	  args = (unsigned short *)(arg + 1);
 132  	  if (user) {
      	  	  int err;
      		  err = verify_area(VERIFY_READ, args, sizeof(short) * 5);
 135  		  if (err)
 136  		  	return err;
      		  get_user(xs, args++);
      		  get_user(ys, args++);
      		  get_user(xe, args++);
      		  get_user(ye, args++);
      		  get_user(sel_mode, args);
 142  	  } else {
      		  xs = *(args++); /* set selection from kernel */
      		  ys = *(args++);
      		  xe = *(args++);
      		  ye = *(args++);
      		  sel_mode = *args;
      	  }
      	  xs--; ys--; xe--; ye--;
      	  xs = limit(xs, video_num_columns - 1);
      	  ys = limit(ys, video_num_lines - 1);
      	  xe = limit(xe, video_num_columns - 1);
      	  ye = limit(ye, video_num_lines - 1);
      	  ps = ys * video_size_row + (xs << 1);
      	  pe = ye * video_size_row + (xe << 1);
      
 157  	  if (sel_mode == 4) {
      	      /* useful for screendump without selection highlights */
      	      clear_selection();
 160  	      return 0;
      	  }
      
 163  	  if (mouse_reporting() && (sel_mode & 16)) {
      	      mouse_report(tty, sel_mode & 15, xs, ys);
 165  	      return 0;
      	  }
              }
      
 169  	if (ps > pe)	/* make sel_start <= sel_end */
      	{
      		int tmp = ps;
      		ps = pe;
      		pe = tmp;
      	}
      
 176  	if (sel_cons != fg_console) {
      		clear_selection();
      		sel_cons = fg_console;
      	}
      
 181  	switch (sel_mode)
      	{
 183  		case 0:	/* character-by-character selection */
      			new_sel_start = ps;
      			new_sel_end = pe;
 186  			break;
 187  		case 1:	/* word-by-word selection */
      			spc = isspace(sel_pos(ps));
 189  			for (new_sel_start = ps; ; ps -= 2)
      			{
      				if ((spc && !isspace(sel_pos(ps))) ||
 192  				    (!spc && !inword(sel_pos(ps))))
 193  					break;
      				new_sel_start = ps;
 195  				if (!(ps % video_size_row))
 196  					break;
      			}
      			spc = isspace(sel_pos(pe));
 199  			for (new_sel_end = pe; ; pe += 2)
      			{
      				if ((spc && !isspace(sel_pos(pe))) ||
 202  				    (!spc && !inword(sel_pos(pe))))
 203  					break;
      				new_sel_end = pe;
 205  				if (!((pe + 2) % video_size_row))
 206  					break;
      			}
 208  			break;
 209  		case 2:	/* line-by-line selection */
      			new_sel_start = ps - ps % video_size_row;
      			new_sel_end = pe + video_size_row
      				    - pe % video_size_row - 2;
 213  			break;
 214  		case 3:
      			highlight_pointer(pe);
 216  			return 0;
 217  		default:
 218  			return -EINVAL;
      	}
      
      	/* remove the pointer */
      	highlight_pointer(-1);
      
      	/* select to end of line if on trailing space */
      	if (new_sel_end > new_sel_start &&
      		!atedge(new_sel_end, video_size_row) &&
 227  		isspace(sel_pos(new_sel_end))) {
 228  		for (pe = new_sel_end + 2; ; pe += 2)
      			if (!isspace(sel_pos(pe)) ||
 230  			    atedge(pe, video_size_row))
 231  				break;
 232  		if (isspace(sel_pos(pe)))
      			new_sel_end = pe;
      	}
 235  	if (sel_start == -1)	/* no current selection */
      		highlight(new_sel_start, new_sel_end);
 237  	else if (new_sel_start == sel_start)
      	{
 239  		if (new_sel_end == sel_end)	/* no action required */
 240  			return 0;
 241  		else if (new_sel_end > sel_end)	/* extend to right */
      			highlight(sel_end + 2, new_sel_end);
 243  		else				/* contract from right */
      			highlight(new_sel_end + 2, sel_end);
      	}
 246  	else if (new_sel_end == sel_end)
      	{
 248  		if (new_sel_start < sel_start)	/* extend to left */
      			highlight(new_sel_start, sel_start - 2);
 250  		else				/* contract from left */
      			highlight(sel_start, new_sel_start - 2);
      	}
 253  	else	/* some other case; start selection from scratch */
      	{
      		clear_selection();
      		highlight(new_sel_start, new_sel_end);
      	}
      	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_KERNEL);
 263  	if (!bp) {
      		printk(KERN_WARNING "selection: kmalloc() failed\n");
      		clear_selection();
 266  		return -ENOMEM;
      	}
 268  	if (sel_buffer)
      		kfree(sel_buffer);
      	sel_buffer = bp;
      
      	obp = bp;
 273  	for (i = sel_start; i <= sel_end; i += 2) {
      		*bp = sel_pos(i);
 275  		if (!isspace(*bp++))
      			obp = bp;
 277  		if (! ((i + 2) % video_size_row)) {
      			/* strip trailing blanks from line and add newline,
      			   unless non-space at end of line. */
 280  			if (obp != bp) {
      				bp = obp;
      				*bp++ = '\r';
      			}
      			obp = bp;
      		}
      	}
      	sel_buffer_lth = bp - sel_buffer;
 288  	return 0;
      }
      
      /* Insert the contents of the selection buffer into the
       * queue of the tty associated with the current console.
       * Invoked by ioctl().
       */
 295  int paste_selection(struct tty_struct *tty)
      {
      	struct vt_struct *vt = (struct vt_struct *) tty->driver_data;
      	int	pasted = 0, count;
      	DECLARE_WAITQUEUE(wait, current);
      
      	poke_blanked_console();
      	add_wait_queue(&vt->paste_wait, &wait);
 303  	while (sel_buffer && sel_buffer_lth > pasted) {
 304  		set_current_state(TASK_INTERRUPTIBLE);
 305  		if (test_bit(TTY_THROTTLED, &tty->flags)) {
      			schedule();
 307  			continue;
      		}
      		count = sel_buffer_lth - pasted;
      		count = MIN(count, tty->ldisc.receive_room(tty));
      		tty->ldisc.receive_buf(tty, sel_buffer + pasted, 0, count);
      		pasted += count;
      	}
      	remove_wait_queue(&vt->paste_wait, &wait);
      	current->state = TASK_RUNNING;
 316  	return 0;
      }
      
      EXPORT_SYMBOL(set_selection);
      EXPORT_SYMBOL(paste_selection);