]> git.ipfire.org Git - people/arne_f/kernel.git/blame - drivers/staging/speakup/selection.c
License cleanup: add SPDX GPL-2.0 license identifier to files with no license
[people/arne_f/kernel.git] / drivers / staging / speakup / selection.c
CommitLineData
b2441318 1// SPDX-License-Identifier: GPL-2.0
c6e3fd22
WH
2#include <linux/slab.h> /* for kmalloc */
3#include <linux/consolemap.h>
4#include <linux/interrupt.h>
5#include <linux/sched.h>
593fb1ae 6#include <linux/device.h> /* for dev_warn */
c6e3fd22 7#include <linux/selection.h>
d7500135 8#include <linux/workqueue.h>
28a821c3
BH
9#include <linux/tty.h>
10#include <linux/tty_flip.h>
84567995 11#include <linux/atomic.h>
c6e3fd22
WH
12
13#include "speakup.h"
14
15/* ------ cut and paste ----- */
16/* Don't take this from <ctype.h>: 011-015 on the screen aren't spaces */
17#define ishardspace(c) ((c) == ' ')
18
ca2beaf8 19unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */
c6e3fd22
WH
20
21/* Variables for selection control. */
c6ac992b 22/* must not be deallocated */
c6e3fd22
WH
23struct vc_data *spk_sel_cons;
24/* cleared by clear_selection */
25static int sel_start = -1;
26static int sel_end;
27static int sel_buffer_lth;
28static char *sel_buffer;
29
30static unsigned char sel_pos(int n)
31{
a1768fbb
WH
32 return inverse_translate(spk_sel_cons,
33 screen_glyph(spk_sel_cons, n), 0);
c6e3fd22
WH
34}
35
36void speakup_clear_selection(void)
37{
38 sel_start = -1;
39}
40
41/* does screen address p correspond to character at LH/RH edge of screen? */
42static int atedge(const int p, int size_row)
43{
a1768fbb 44 return !(p % size_row) || !((p + 2) % size_row);
c6e3fd22
WH
45}
46
47/* constrain v such that v <= u */
48static unsigned short limit(const unsigned short v, const unsigned short u)
49{
50 return (v > u) ? u : v;
51}
52
53int speakup_set_selection(struct tty_struct *tty)
54{
55 int new_sel_start, new_sel_end;
56 char *bp, *obp;
57 int i, ps, pe;
58 struct vc_data *vc = vc_cons[fg_console].d;
59
ca2beaf8
ST
60 spk_xs = limit(spk_xs, vc->vc_cols - 1);
61 spk_ys = limit(spk_ys, vc->vc_rows - 1);
62 spk_xe = limit(spk_xe, vc->vc_cols - 1);
63 spk_ye = limit(spk_ye, vc->vc_rows - 1);
64 ps = spk_ys * vc->vc_size_row + (spk_xs << 1);
65 pe = spk_ye * vc->vc_size_row + (spk_xe << 1);
c6e3fd22
WH
66
67 if (ps > pe) {
68 /* make sel_start <= sel_end */
69 int tmp = ps;
c772bce6 70
c6e3fd22
WH
71 ps = pe;
72 pe = tmp;
73 }
74
75 if (spk_sel_cons != vc_cons[fg_console].d) {
76 speakup_clear_selection();
77 spk_sel_cons = vc_cons[fg_console].d;
e888fabd 78 dev_warn(tty->dev,
65bf4ea1 79 "Selection: mark console not the same as cut\n");
c6e3fd22
WH
80 return -EINVAL;
81 }
82
83 new_sel_start = ps;
84 new_sel_end = pe;
85
86 /* select to end of line if on trailing space */
87 if (new_sel_end > new_sel_start &&
88 !atedge(new_sel_end, vc->vc_size_row) &&
89 ishardspace(sel_pos(new_sel_end))) {
90 for (pe = new_sel_end + 2; ; pe += 2)
91 if (!ishardspace(sel_pos(pe)) ||
92 atedge(pe, vc->vc_size_row))
93 break;
94 if (ishardspace(sel_pos(pe)))
95 new_sel_end = pe;
96 }
97 if ((new_sel_start == sel_start) && (new_sel_end == sel_end))
98 return 0; /* no action required */
99
100 sel_start = new_sel_start;
101 sel_end = new_sel_end;
102 /* Allocate a new buffer before freeing the old one ... */
e3bab5eb 103 bp = kmalloc((sel_end - sel_start) / 2 + 1, GFP_ATOMIC);
c6e3fd22 104 if (!bp) {
c6e3fd22
WH
105 speakup_clear_selection();
106 return -ENOMEM;
107 }
108 kfree(sel_buffer);
109 sel_buffer = bp;
110
111 obp = bp;
112 for (i = sel_start; i <= sel_end; i += 2) {
113 *bp = sel_pos(i);
114 if (!ishardspace(*bp++))
115 obp = bp;
116 if (!((i + 2) % vc->vc_size_row)) {
117 /* strip trailing blanks from line and add newline,
13d825ed
AF
118 * unless non-space at end of line.
119 */
c6e3fd22
WH
120 if (obp != bp) {
121 bp = obp;
122 *bp++ = '\r';
123 }
124 obp = bp;
125 }
126 }
127 sel_buffer_lth = bp - sel_buffer;
128 return 0;
129}
130
d7500135
BH
131struct speakup_paste_work {
132 struct work_struct work;
133 struct tty_struct *tty;
134};
135
136static void __speakup_paste_selection(struct work_struct *work)
c6e3fd22 137{
d7500135
BH
138 struct speakup_paste_work *spw =
139 container_of(work, struct speakup_paste_work, work);
140 struct tty_struct *tty = xchg(&spw->tty, NULL);
d290effe 141 struct vc_data *vc = (struct vc_data *)tty->driver_data;
c6e3fd22 142 int pasted = 0, count;
28a821c3 143 struct tty_ldisc *ld;
c6e3fd22 144 DECLARE_WAITQUEUE(wait, current);
d7500135 145
f4f9edcf
PH
146 ld = tty_ldisc_ref(tty);
147 if (!ld)
148 goto tty_unref;
28a821c3
BH
149 tty_buffer_lock_exclusive(&vc->port);
150
c6e3fd22
WH
151 add_wait_queue(&vc->paste_wait, &wait);
152 while (sel_buffer && sel_buffer_lth > pasted) {
153 set_current_state(TASK_INTERRUPTIBLE);
97ef38b8 154 if (tty_throttled(tty)) {
c6e3fd22
WH
155 schedule();
156 continue;
157 }
158 count = sel_buffer_lth - pasted;
28a821c3
BH
159 count = tty_ldisc_receive_buf(ld, sel_buffer + pasted, NULL,
160 count);
c6e3fd22
WH
161 pasted += count;
162 }
163 remove_wait_queue(&vc->paste_wait, &wait);
2be90fef 164 __set_current_state(TASK_RUNNING);
28a821c3
BH
165
166 tty_buffer_unlock_exclusive(&vc->port);
167 tty_ldisc_deref(ld);
f4f9edcf 168tty_unref:
d7500135
BH
169 tty_kref_put(tty);
170}
171
172static struct speakup_paste_work speakup_paste_work = {
173 .work = __WORK_INITIALIZER(speakup_paste_work.work,
174 __speakup_paste_selection)
175};
176
177int speakup_paste_selection(struct tty_struct *tty)
178{
b8f107bc 179 if (cmpxchg(&speakup_paste_work.tty, NULL, tty))
d7500135
BH
180 return -EBUSY;
181
182 tty_kref_get(tty);
183 schedule_work_on(WORK_CPU_UNBOUND, &speakup_paste_work.work);
c6e3fd22
WH
184 return 0;
185}
186
d7500135
BH
187void speakup_cancel_paste(void)
188{
189 cancel_work_sync(&speakup_paste_work.work);
190 tty_kref_put(speakup_paste_work.tty);
191}