Commit | Line | Data |
---|---|---|
c6e3fd22 WH |
1 | #include <linux/slab.h> /* for kmalloc */ |
2 | #include <linux/consolemap.h> | |
3 | #include <linux/interrupt.h> | |
4 | #include <linux/sched.h> | |
593fb1ae | 5 | #include <linux/device.h> /* for dev_warn */ |
c6e3fd22 WH |
6 | #include <linux/selection.h> |
7 | ||
8 | #include "speakup.h" | |
9 | ||
10 | /* ------ cut and paste ----- */ | |
11 | /* Don't take this from <ctype.h>: 011-015 on the screen aren't spaces */ | |
12 | #define ishardspace(c) ((c) == ' ') | |
13 | ||
ca2beaf8 | 14 | unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */ |
c6e3fd22 WH |
15 | |
16 | /* Variables for selection control. */ | |
c6ac992b | 17 | /* must not be deallocated */ |
c6e3fd22 WH |
18 | struct vc_data *spk_sel_cons; |
19 | /* cleared by clear_selection */ | |
20 | static int sel_start = -1; | |
21 | static int sel_end; | |
22 | static int sel_buffer_lth; | |
23 | static char *sel_buffer; | |
24 | ||
25 | static unsigned char sel_pos(int n) | |
26 | { | |
a1768fbb WH |
27 | return inverse_translate(spk_sel_cons, |
28 | screen_glyph(spk_sel_cons, n), 0); | |
c6e3fd22 WH |
29 | } |
30 | ||
31 | void speakup_clear_selection(void) | |
32 | { | |
33 | sel_start = -1; | |
34 | } | |
35 | ||
36 | /* does screen address p correspond to character at LH/RH edge of screen? */ | |
37 | static int atedge(const int p, int size_row) | |
38 | { | |
a1768fbb | 39 | return !(p % size_row) || !((p + 2) % size_row); |
c6e3fd22 WH |
40 | } |
41 | ||
42 | /* constrain v such that v <= u */ | |
43 | static unsigned short limit(const unsigned short v, const unsigned short u) | |
44 | { | |
45 | return (v > u) ? u : v; | |
46 | } | |
47 | ||
48 | int speakup_set_selection(struct tty_struct *tty) | |
49 | { | |
50 | int new_sel_start, new_sel_end; | |
51 | char *bp, *obp; | |
52 | int i, ps, pe; | |
53 | struct vc_data *vc = vc_cons[fg_console].d; | |
54 | ||
ca2beaf8 ST |
55 | spk_xs = limit(spk_xs, vc->vc_cols - 1); |
56 | spk_ys = limit(spk_ys, vc->vc_rows - 1); | |
57 | spk_xe = limit(spk_xe, vc->vc_cols - 1); | |
58 | spk_ye = limit(spk_ye, vc->vc_rows - 1); | |
59 | ps = spk_ys * vc->vc_size_row + (spk_xs << 1); | |
60 | pe = spk_ye * vc->vc_size_row + (spk_xe << 1); | |
c6e3fd22 WH |
61 | |
62 | if (ps > pe) { | |
63 | /* make sel_start <= sel_end */ | |
64 | int tmp = ps; | |
65 | ps = pe; | |
66 | pe = tmp; | |
67 | } | |
68 | ||
69 | if (spk_sel_cons != vc_cons[fg_console].d) { | |
70 | speakup_clear_selection(); | |
71 | spk_sel_cons = vc_cons[fg_console].d; | |
e888fabd | 72 | dev_warn(tty->dev, |
c6e3fd22 WH |
73 | "Selection: mark console not the same as cut\n"); |
74 | return -EINVAL; | |
75 | } | |
76 | ||
77 | new_sel_start = ps; | |
78 | new_sel_end = pe; | |
79 | ||
80 | /* select to end of line if on trailing space */ | |
81 | if (new_sel_end > new_sel_start && | |
82 | !atedge(new_sel_end, vc->vc_size_row) && | |
83 | ishardspace(sel_pos(new_sel_end))) { | |
84 | for (pe = new_sel_end + 2; ; pe += 2) | |
85 | if (!ishardspace(sel_pos(pe)) || | |
86 | atedge(pe, vc->vc_size_row)) | |
87 | break; | |
88 | if (ishardspace(sel_pos(pe))) | |
89 | new_sel_end = pe; | |
90 | } | |
91 | if ((new_sel_start == sel_start) && (new_sel_end == sel_end)) | |
92 | return 0; /* no action required */ | |
93 | ||
94 | sel_start = new_sel_start; | |
95 | sel_end = new_sel_end; | |
96 | /* Allocate a new buffer before freeing the old one ... */ | |
97 | bp = kmalloc((sel_end-sel_start)/2+1, GFP_ATOMIC); | |
98 | if (!bp) { | |
c6e3fd22 WH |
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()) | |
a1768fbb | 136 | /* if we are in an interrupt handler, abort */ |
c6e3fd22 WH |
137 | break; |
138 | schedule(); | |
139 | continue; | |
140 | } | |
141 | count = sel_buffer_lth - pasted; | |
142 | count = min_t(int, count, tty->receive_room); | |
a1768fbb | 143 | tty->ldisc->ops->receive_buf(tty, sel_buffer + pasted, |
e888fabd | 144 | NULL, count); |
c6e3fd22 WH |
145 | pasted += count; |
146 | } | |
147 | remove_wait_queue(&vc->paste_wait, &wait); | |
148 | current->state = TASK_RUNNING; | |
149 | return 0; | |
150 | } | |
151 |