vserver 1.9.5.x5
[linux-2.6.git] / drivers / char / selection.c
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
23 #include <linux/vt_kern.h>
24 #include <linux/consolemap.h>
25 #include <linux/selection.h>
26 #include <linux/tiocl.h>
27 #include <linux/console.h>
28
29 /* Don't take this from <ctype.h>: 011-015 on the screen aren't spaces */
30 #define isspace(c)      ((c) == ' ')
31
32 extern void poke_blanked_console(void);
33
34 /* Variables for selection control. */
35 /* Use a dynamic buffer, instead of static (Dec 1994) */
36 struct vc_data *sel_cons;               /* must not be disallocated */
37 static volatile int sel_start = -1;     /* cleared by clear_selection */
38 static int sel_end;
39 static int sel_buffer_lth;
40 static char *sel_buffer;
41
42 /* clear_selection, highlight and highlight_pointer can be called
43    from interrupt (via scrollback/front) */
44
45 /* set reverse video on characters s-e of console with selection. */
46 inline static void
47 highlight(const int s, const int e)
48 {
49         invert_screen(sel_cons, s, e-s+2, 1);
50 }
51
52 /* use complementary color to show the pointer */
53 inline static void
54 highlight_pointer(const int where)
55 {
56         complement_pos(sel_cons, where);
57 }
58
59 static unsigned char
60 sel_pos(int n)
61 {
62         return inverse_translate(sel_cons, screen_glyph(sel_cons, n));
63 }
64
65 /* remove the current selection highlight, if any,
66    from the console holding the selection. */
67 void
68 clear_selection(void) {
69         highlight_pointer(-1); /* hide the pointer */
70         if (sel_start != -1) {
71                 highlight(sel_start, sel_end);
72                 sel_start = -1;
73         }
74 }
75
76 /*
77  * User settable table: what characters are to be considered alphabetic?
78  * 256 bits
79  */
80 static u32 inwordLut[8]={
81   0x00000000, /* control chars     */
82   0x03FF0000, /* digits            */
83   0x87FFFFFE, /* uppercase and '_' */
84   0x07FFFFFE, /* lowercase         */
85   0x00000000,
86   0x00000000,
87   0xFF7FFFFF, /* latin-1 accented letters, not multiplication sign */
88   0xFF7FFFFF  /* latin-1 accented letters, not division sign */
89 };
90
91 static inline int inword(const unsigned char c) {
92         return ( inwordLut[c>>5] >> (c & 0x1F) ) & 1;
93 }
94
95 /* set inwordLut contents. Invoked by ioctl(). */
96 int sel_loadlut(char __user *p)
97 {
98         return copy_from_user(inwordLut, (u32 __user *)(p+4), 32) ? -EFAULT : 0;
99 }
100
101 /* does screen address p correspond to character at LH/RH edge of screen? */
102 static inline int atedge(const int p, int size_row)
103 {
104         return (!(p % size_row) || !((p + 2) % size_row));
105 }
106
107 /* constrain v such that v <= u */
108 static inline unsigned short limit(const unsigned short v, const unsigned short u)
109 {
110         return (v > u) ? u : v;
111 }
112
113 /* set the current selection. Invoked by ioctl() or by kernel code. */
114 int set_selection(const struct tiocl_selection __user *sel, struct tty_struct *tty)
115 {
116         struct vc_data *vc = vc_cons[fg_console].d;
117         int sel_mode, new_sel_start, new_sel_end, spc;
118         char *bp, *obp;
119         int i, ps, pe;
120
121         poke_blanked_console();
122
123         { unsigned short xs, ys, xe, ye;
124
125           if (verify_area(VERIFY_READ, sel, sizeof(*sel)))
126                 return -EFAULT;
127           __get_user(xs, &sel->xs);
128           __get_user(ys, &sel->ys);
129           __get_user(xe, &sel->xe);
130           __get_user(ye, &sel->ye);
131           __get_user(sel_mode, &sel->sel_mode);
132           xs--; ys--; xe--; ye--;
133           xs = limit(xs, vc->vc_cols - 1);
134           ys = limit(ys, vc->vc_rows - 1);
135           xe = limit(xe, vc->vc_cols - 1);
136           ye = limit(ye, vc->vc_rows - 1);
137           ps = ys * vc->vc_size_row + (xs << 1);
138           pe = ye * vc->vc_size_row + (xe << 1);
139
140           if (sel_mode == TIOCL_SELCLEAR) {
141               /* useful for screendump without selection highlights */
142               clear_selection();
143               return 0;
144           }
145
146           if (mouse_reporting() && (sel_mode & TIOCL_SELMOUSEREPORT)) {
147               mouse_report(tty, sel_mode & TIOCL_SELBUTTONMASK, xs, ys);
148               return 0;
149           }
150         }
151
152         if (ps > pe)    /* make sel_start <= sel_end */
153         {
154                 int tmp = ps;
155                 ps = pe;
156                 pe = tmp;
157         }
158
159         if (sel_cons != vc_cons[fg_console].d) {
160                 clear_selection();
161                 sel_cons = vc_cons[fg_console].d;
162         }
163
164         switch (sel_mode)
165         {
166                 case TIOCL_SELCHAR:     /* character-by-character selection */
167                         new_sel_start = ps;
168                         new_sel_end = pe;
169                         break;
170                 case TIOCL_SELWORD:     /* word-by-word selection */
171                         spc = isspace(sel_pos(ps));
172                         for (new_sel_start = ps; ; ps -= 2)
173                         {
174                                 if ((spc && !isspace(sel_pos(ps))) ||
175                                     (!spc && !inword(sel_pos(ps))))
176                                         break;
177                                 new_sel_start = ps;
178                                 if (!(ps % vc->vc_size_row))
179                                         break;
180                         }
181                         spc = isspace(sel_pos(pe));
182                         for (new_sel_end = pe; ; pe += 2)
183                         {
184                                 if ((spc && !isspace(sel_pos(pe))) ||
185                                     (!spc && !inword(sel_pos(pe))))
186                                         break;
187                                 new_sel_end = pe;
188                                 if (!((pe + 2) % vc->vc_size_row))
189                                         break;
190                         }
191                         break;
192                 case TIOCL_SELLINE:     /* line-by-line selection */
193                         new_sel_start = ps - ps % vc->vc_size_row;
194                         new_sel_end = pe + vc->vc_size_row
195                                     - pe % vc->vc_size_row - 2;
196                         break;
197                 case TIOCL_SELPOINTER:
198                         highlight_pointer(pe);
199                         return 0;
200                 default:
201                         return -EINVAL;
202         }
203
204         /* remove the pointer */
205         highlight_pointer(-1);
206
207         /* select to end of line if on trailing space */
208         if (new_sel_end > new_sel_start &&
209                 !atedge(new_sel_end, vc->vc_size_row) &&
210                 isspace(sel_pos(new_sel_end))) {
211                 for (pe = new_sel_end + 2; ; pe += 2)
212                         if (!isspace(sel_pos(pe)) ||
213                             atedge(pe, vc->vc_size_row))
214                                 break;
215                 if (isspace(sel_pos(pe)))
216                         new_sel_end = pe;
217         }
218         if (sel_start == -1)    /* no current selection */
219                 highlight(new_sel_start, new_sel_end);
220         else if (new_sel_start == sel_start)
221         {
222                 if (new_sel_end == sel_end)     /* no action required */
223                         return 0;
224                 else if (new_sel_end > sel_end) /* extend to right */
225                         highlight(sel_end + 2, new_sel_end);
226                 else                            /* contract from right */
227                         highlight(new_sel_end + 2, sel_end);
228         }
229         else if (new_sel_end == sel_end)
230         {
231                 if (new_sel_start < sel_start)  /* extend to left */
232                         highlight(new_sel_start, sel_start - 2);
233                 else                            /* contract from left */
234                         highlight(sel_start, new_sel_start - 2);
235         }
236         else    /* some other case; start selection from scratch */
237         {
238                 clear_selection();
239                 highlight(new_sel_start, new_sel_end);
240         }
241         sel_start = new_sel_start;
242         sel_end = new_sel_end;
243
244         /* Allocate a new buffer before freeing the old one ... */
245         bp = kmalloc((sel_end-sel_start)/2+1, GFP_KERNEL);
246         if (!bp) {
247                 printk(KERN_WARNING "selection: kmalloc() failed\n");
248                 clear_selection();
249                 return -ENOMEM;
250         }
251         if (sel_buffer)
252                 kfree(sel_buffer);
253         sel_buffer = bp;
254
255         obp = bp;
256         for (i = sel_start; i <= sel_end; i += 2) {
257                 *bp = sel_pos(i);
258                 if (!isspace(*bp++))
259                         obp = bp;
260                 if (! ((i + 2) % vc->vc_size_row)) {
261                         /* strip trailing blanks from line and add newline,
262                            unless non-space at end of line. */
263                         if (obp != bp) {
264                                 bp = obp;
265                                 *bp++ = '\r';
266                         }
267                         obp = bp;
268                 }
269         }
270         sel_buffer_lth = bp - sel_buffer;
271         return 0;
272 }
273
274 /* Insert the contents of the selection buffer into the
275  * queue of the tty associated with the current console.
276  * Invoked by ioctl().
277  */
278 int paste_selection(struct tty_struct *tty)
279 {
280         struct vt_struct *vt = (struct vt_struct *) tty->driver_data;
281         int     pasted = 0, count;
282         struct  tty_ldisc *ld;
283         DECLARE_WAITQUEUE(wait, current);
284
285         acquire_console_sem();
286         poke_blanked_console();
287         release_console_sem();
288
289         ld = tty_ldisc_ref_wait(tty);
290         
291         add_wait_queue(&vt->paste_wait, &wait);
292         while (sel_buffer && sel_buffer_lth > pasted) {
293                 set_current_state(TASK_INTERRUPTIBLE);
294                 if (test_bit(TTY_THROTTLED, &tty->flags)) {
295                         schedule();
296                         continue;
297                 }
298                 count = sel_buffer_lth - pasted;
299                 count = min(count, tty->ldisc.receive_room(tty));
300                 tty->ldisc.receive_buf(tty, sel_buffer + pasted, NULL, count);
301                 pasted += count;
302         }
303         remove_wait_queue(&vt->paste_wait, &wait);
304         current->state = TASK_RUNNING;
305
306         tty_ldisc_deref(ld);
307         return 0;
308 }