Merge git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging-2.6
[pandora-kernel.git] / drivers / staging / speakup / selection.c
1 #include <linux/slab.h> /* for kmalloc */
2 #include <linux/consolemap.h>
3 #include <linux/interrupt.h>
4 #include <linux/sched.h>
5 #include <linux/selection.h>
6
7 #include "speakup.h"
8
9 /* ------ cut and paste ----- */
10 /* Don't take this from <ctype.h>: 011-015 on the screen aren't spaces */
11 #define ishardspace(c)      ((c) == ' ')
12
13 unsigned short xs, ys, xe, ye; /* our region points */
14
15 /* Variables for selection control. */
16 /* must not be disallocated */
17 struct vc_data *spk_sel_cons;
18 /* cleared by clear_selection */
19 static int sel_start = -1;
20 static int sel_end;
21 static int sel_buffer_lth;
22 static char *sel_buffer;
23
24 static unsigned char sel_pos(int n)
25 {
26         return inverse_translate(spk_sel_cons,
27                 screen_glyph(spk_sel_cons, n), 0);
28 }
29
30 void speakup_clear_selection(void)
31 {
32         sel_start = -1;
33 }
34
35 /* does screen address p correspond to character at LH/RH edge of screen? */
36 static int atedge(const int p, int size_row)
37 {
38         return !(p % size_row) || !((p + 2) % size_row);
39 }
40
41 /* constrain v such that v <= u */
42 static unsigned short limit(const unsigned short v, const unsigned short u)
43 {
44         return (v > u) ? u : v;
45 }
46
47 int speakup_set_selection(struct tty_struct *tty)
48 {
49         int new_sel_start, new_sel_end;
50         char *bp, *obp;
51         int i, ps, pe;
52         struct vc_data *vc = vc_cons[fg_console].d;
53
54         xs = limit(xs, vc->vc_cols - 1);
55         ys = limit(ys, vc->vc_rows - 1);
56         xe = limit(xe, vc->vc_cols - 1);
57         ye = limit(ye, vc->vc_rows - 1);
58         ps = ys * vc->vc_size_row + (xs << 1);
59         pe = ye * vc->vc_size_row + (xe << 1);
60
61         if (ps > pe) {
62                 /* make sel_start <= sel_end */
63                 int tmp = ps;
64                 ps = pe;
65                 pe = tmp;
66         }
67
68         if (spk_sel_cons != vc_cons[fg_console].d) {
69                 speakup_clear_selection();
70                 spk_sel_cons = vc_cons[fg_console].d;
71                 printk(KERN_WARNING
72                         "Selection: mark console not the same as cut\n");
73                 return -EINVAL;
74         }
75
76         new_sel_start = ps;
77         new_sel_end = pe;
78
79         /* select to end of line if on trailing space */
80         if (new_sel_end > new_sel_start &&
81             !atedge(new_sel_end, vc->vc_size_row) &&
82             ishardspace(sel_pos(new_sel_end))) {
83                 for (pe = new_sel_end + 2; ; pe += 2)
84                         if (!ishardspace(sel_pos(pe)) ||
85                             atedge(pe, vc->vc_size_row))
86                                 break;
87                 if (ishardspace(sel_pos(pe)))
88                         new_sel_end = pe;
89         }
90         if ((new_sel_start == sel_start) && (new_sel_end == sel_end))
91                 return 0; /* no action required */
92
93         sel_start = new_sel_start;
94         sel_end = new_sel_end;
95         /* Allocate a new buffer before freeing the old one ... */
96         bp = kmalloc((sel_end-sel_start)/2+1, GFP_ATOMIC);
97         if (!bp) {
98                 printk(KERN_WARNING "selection: kmalloc() failed\n");
99                 speakup_clear_selection();
100                 return -ENOMEM;
101         }
102         kfree(sel_buffer);
103         sel_buffer = bp;
104
105         obp = bp;
106         for (i = sel_start; i <= sel_end; i += 2) {
107                 *bp = sel_pos(i);
108                 if (!ishardspace(*bp++))
109                         obp = bp;
110                 if (!((i + 2) % vc->vc_size_row)) {
111                         /* strip trailing blanks from line and add newline,
112                            unless non-space at end of line. */
113                         if (obp != bp) {
114                                 bp = obp;
115                                 *bp++ = '\r';
116                         }
117                         obp = bp;
118                 }
119         }
120         sel_buffer_lth = bp - sel_buffer;
121         return 0;
122 }
123
124 /* TODO: move to some helper thread, probably.  That'd fix having to check for
125  * in_atomic().  */
126 int speakup_paste_selection(struct tty_struct *tty)
127 {
128         struct vc_data *vc = (struct vc_data *) tty->driver_data;
129         int pasted = 0, count;
130         DECLARE_WAITQUEUE(wait, current);
131         add_wait_queue(&vc->paste_wait, &wait);
132         while (sel_buffer && sel_buffer_lth > pasted) {
133                 set_current_state(TASK_INTERRUPTIBLE);
134                 if (test_bit(TTY_THROTTLED, &tty->flags)) {
135                         if (in_atomic())
136                                 /* if we are in an interrupt handler, abort */
137                                 break;
138                         schedule();
139                         continue;
140                 }
141                 count = sel_buffer_lth - pasted;
142                 count = min_t(int, count, tty->receive_room);
143                 tty->ldisc->ops->receive_buf(tty, sel_buffer + pasted,
144                         0, count);
145                 pasted += count;
146         }
147         remove_wait_queue(&vc->paste_wait, &wait);
148         current->state = TASK_RUNNING;
149         return 0;
150 }
151