William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 1 | #include <linux/slab.h> /* for kmalloc */ |
| 2 | #include <linux/consolemap.h> |
| 3 | #include <linux/interrupt.h> |
| 4 | #include <linux/sched.h> |
George Spelvin | 593fb1ae4 | 2013-02-12 02:00:43 -0500 | [diff] [blame] | 5 | #include <linux/device.h> /* for dev_warn */ |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 6 | #include <linux/selection.h> |
Ben Hutchings | d750013 | 2014-05-19 00:56:22 +0100 | [diff] [blame] | 7 | #include <linux/workqueue.h> |
Ben Hutchings | 28a821c | 2014-05-19 01:03:06 +0100 | [diff] [blame] | 8 | #include <linux/tty.h> |
| 9 | #include <linux/tty_flip.h> |
Boqun Feng | 8456799 | 2015-08-26 19:52:46 +0800 | [diff] [blame] | 10 | #include <linux/atomic.h> |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 11 | |
| 12 | #include "speakup.h" |
| 13 | |
| 14 | /* ------ cut and paste ----- */ |
| 15 | /* Don't take this from <ctype.h>: 011-015 on the screen aren't spaces */ |
| 16 | #define ishardspace(c) ((c) == ' ') |
| 17 | |
Samuel Thibault | ca2beaf | 2013-01-02 02:37:40 +0100 | [diff] [blame] | 18 | unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */ |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 19 | |
| 20 | /* Variables for selection control. */ |
Valentin Ilie | c6ac992 | 2013-04-05 15:42:26 +0300 | [diff] [blame] | 21 | /* must not be deallocated */ |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 22 | struct vc_data *spk_sel_cons; |
| 23 | /* cleared by clear_selection */ |
| 24 | static int sel_start = -1; |
| 25 | static int sel_end; |
| 26 | static int sel_buffer_lth; |
| 27 | static char *sel_buffer; |
| 28 | |
| 29 | static unsigned char sel_pos(int n) |
| 30 | { |
William Hubbs | a1768fb | 2010-10-15 22:13:35 -0500 | [diff] [blame] | 31 | return inverse_translate(spk_sel_cons, |
| 32 | screen_glyph(spk_sel_cons, n), 0); |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 33 | } |
| 34 | |
| 35 | void speakup_clear_selection(void) |
| 36 | { |
| 37 | sel_start = -1; |
| 38 | } |
| 39 | |
| 40 | /* does screen address p correspond to character at LH/RH edge of screen? */ |
| 41 | static int atedge(const int p, int size_row) |
| 42 | { |
William Hubbs | a1768fb | 2010-10-15 22:13:35 -0500 | [diff] [blame] | 43 | return !(p % size_row) || !((p + 2) % size_row); |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 44 | } |
| 45 | |
| 46 | /* constrain v such that v <= u */ |
| 47 | static unsigned short limit(const unsigned short v, const unsigned short u) |
| 48 | { |
| 49 | return (v > u) ? u : v; |
| 50 | } |
| 51 | |
| 52 | int speakup_set_selection(struct tty_struct *tty) |
| 53 | { |
| 54 | int new_sel_start, new_sel_end; |
| 55 | char *bp, *obp; |
| 56 | int i, ps, pe; |
| 57 | struct vc_data *vc = vc_cons[fg_console].d; |
| 58 | |
Samuel Thibault | ca2beaf | 2013-01-02 02:37:40 +0100 | [diff] [blame] | 59 | spk_xs = limit(spk_xs, vc->vc_cols - 1); |
| 60 | spk_ys = limit(spk_ys, vc->vc_rows - 1); |
| 61 | spk_xe = limit(spk_xe, vc->vc_cols - 1); |
| 62 | spk_ye = limit(spk_ye, vc->vc_rows - 1); |
| 63 | ps = spk_ys * vc->vc_size_row + (spk_xs << 1); |
| 64 | pe = spk_ye * vc->vc_size_row + (spk_xe << 1); |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 65 | |
| 66 | if (ps > pe) { |
| 67 | /* make sel_start <= sel_end */ |
| 68 | int tmp = ps; |
Aybuke Ozdemir | c772bce | 2014-09-26 22:26:49 +0300 | [diff] [blame] | 69 | |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 70 | ps = pe; |
| 71 | pe = tmp; |
| 72 | } |
| 73 | |
| 74 | if (spk_sel_cons != vc_cons[fg_console].d) { |
| 75 | speakup_clear_selection(); |
| 76 | spk_sel_cons = vc_cons[fg_console].d; |
Chris Yungmann | e888fab | 2012-06-13 12:39:55 -0400 | [diff] [blame] | 77 | dev_warn(tty->dev, |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 78 | "Selection: mark console not the same as cut\n"); |
| 79 | return -EINVAL; |
| 80 | } |
| 81 | |
| 82 | new_sel_start = ps; |
| 83 | new_sel_end = pe; |
| 84 | |
| 85 | /* select to end of line if on trailing space */ |
| 86 | if (new_sel_end > new_sel_start && |
| 87 | !atedge(new_sel_end, vc->vc_size_row) && |
| 88 | ishardspace(sel_pos(new_sel_end))) { |
| 89 | for (pe = new_sel_end + 2; ; pe += 2) |
| 90 | if (!ishardspace(sel_pos(pe)) || |
| 91 | atedge(pe, vc->vc_size_row)) |
| 92 | break; |
| 93 | if (ishardspace(sel_pos(pe))) |
| 94 | new_sel_end = pe; |
| 95 | } |
| 96 | if ((new_sel_start == sel_start) && (new_sel_end == sel_end)) |
| 97 | return 0; /* no action required */ |
| 98 | |
| 99 | sel_start = new_sel_start; |
| 100 | sel_end = new_sel_end; |
| 101 | /* Allocate a new buffer before freeing the old one ... */ |
| 102 | bp = kmalloc((sel_end-sel_start)/2+1, GFP_ATOMIC); |
| 103 | if (!bp) { |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 104 | speakup_clear_selection(); |
| 105 | return -ENOMEM; |
| 106 | } |
| 107 | kfree(sel_buffer); |
| 108 | sel_buffer = bp; |
| 109 | |
| 110 | obp = bp; |
| 111 | for (i = sel_start; i <= sel_end; i += 2) { |
| 112 | *bp = sel_pos(i); |
| 113 | if (!ishardspace(*bp++)) |
| 114 | obp = bp; |
| 115 | if (!((i + 2) % vc->vc_size_row)) { |
| 116 | /* strip trailing blanks from line and add newline, |
Aleksei Fedotov | 13d825e | 2015-08-14 22:34:37 +0300 | [diff] [blame] | 117 | * unless non-space at end of line. |
| 118 | */ |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 119 | if (obp != bp) { |
| 120 | bp = obp; |
| 121 | *bp++ = '\r'; |
| 122 | } |
| 123 | obp = bp; |
| 124 | } |
| 125 | } |
| 126 | sel_buffer_lth = bp - sel_buffer; |
| 127 | return 0; |
| 128 | } |
| 129 | |
Ben Hutchings | d750013 | 2014-05-19 00:56:22 +0100 | [diff] [blame] | 130 | struct speakup_paste_work { |
| 131 | struct work_struct work; |
| 132 | struct tty_struct *tty; |
| 133 | }; |
| 134 | |
| 135 | static void __speakup_paste_selection(struct work_struct *work) |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 136 | { |
Ben Hutchings | d750013 | 2014-05-19 00:56:22 +0100 | [diff] [blame] | 137 | struct speakup_paste_work *spw = |
| 138 | container_of(work, struct speakup_paste_work, work); |
| 139 | struct tty_struct *tty = xchg(&spw->tty, NULL); |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 140 | struct vc_data *vc = (struct vc_data *) tty->driver_data; |
| 141 | int pasted = 0, count; |
Ben Hutchings | 28a821c | 2014-05-19 01:03:06 +0100 | [diff] [blame] | 142 | struct tty_ldisc *ld; |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 143 | DECLARE_WAITQUEUE(wait, current); |
Ben Hutchings | d750013 | 2014-05-19 00:56:22 +0100 | [diff] [blame] | 144 | |
Peter Hurley | f4f9edc | 2016-01-10 22:40:58 -0800 | [diff] [blame] | 145 | ld = tty_ldisc_ref(tty); |
| 146 | if (!ld) |
| 147 | goto tty_unref; |
Ben Hutchings | 28a821c | 2014-05-19 01:03:06 +0100 | [diff] [blame] | 148 | tty_buffer_lock_exclusive(&vc->port); |
| 149 | |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 150 | add_wait_queue(&vc->paste_wait, &wait); |
| 151 | while (sel_buffer && sel_buffer_lth > pasted) { |
| 152 | set_current_state(TASK_INTERRUPTIBLE); |
Peter Hurley | 97ef38b | 2016-04-09 17:11:36 -0700 | [diff] [blame] | 153 | if (tty_throttled(tty)) { |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 154 | schedule(); |
| 155 | continue; |
| 156 | } |
| 157 | count = sel_buffer_lth - pasted; |
Ben Hutchings | 28a821c | 2014-05-19 01:03:06 +0100 | [diff] [blame] | 158 | count = tty_ldisc_receive_buf(ld, sel_buffer + pasted, NULL, |
| 159 | count); |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 160 | pasted += count; |
| 161 | } |
| 162 | remove_wait_queue(&vc->paste_wait, &wait); |
Davidlohr Bueso | 2be90fe | 2015-01-26 02:15:02 -0800 | [diff] [blame] | 163 | __set_current_state(TASK_RUNNING); |
Ben Hutchings | 28a821c | 2014-05-19 01:03:06 +0100 | [diff] [blame] | 164 | |
| 165 | tty_buffer_unlock_exclusive(&vc->port); |
| 166 | tty_ldisc_deref(ld); |
Peter Hurley | f4f9edc | 2016-01-10 22:40:58 -0800 | [diff] [blame] | 167 | tty_unref: |
Ben Hutchings | d750013 | 2014-05-19 00:56:22 +0100 | [diff] [blame] | 168 | tty_kref_put(tty); |
| 169 | } |
| 170 | |
| 171 | static struct speakup_paste_work speakup_paste_work = { |
| 172 | .work = __WORK_INITIALIZER(speakup_paste_work.work, |
| 173 | __speakup_paste_selection) |
| 174 | }; |
| 175 | |
| 176 | int speakup_paste_selection(struct tty_struct *tty) |
| 177 | { |
| 178 | if (cmpxchg(&speakup_paste_work.tty, NULL, tty) != NULL) |
| 179 | return -EBUSY; |
| 180 | |
| 181 | tty_kref_get(tty); |
| 182 | schedule_work_on(WORK_CPU_UNBOUND, &speakup_paste_work.work); |
William Hubbs | c6e3fd2 | 2010-10-07 13:20:02 -0500 | [diff] [blame] | 183 | return 0; |
| 184 | } |
| 185 | |
Ben Hutchings | d750013 | 2014-05-19 00:56:22 +0100 | [diff] [blame] | 186 | void speakup_cancel_paste(void) |
| 187 | { |
| 188 | cancel_work_sync(&speakup_paste_work.work); |
| 189 | tty_kref_put(speakup_paste_work.tty); |
| 190 | } |