Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
at v4.20 186 lines 4.7 kB view raw
1// SPDX-License-Identifier: GPL-2.0 2#include <linux/slab.h> /* for kmalloc */ 3#include <linux/consolemap.h> 4#include <linux/interrupt.h> 5#include <linux/sched.h> 6#include <linux/device.h> /* for dev_warn */ 7#include <linux/selection.h> 8#include <linux/workqueue.h> 9#include <linux/tty.h> 10#include <linux/tty_flip.h> 11#include <linux/atomic.h> 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 19unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */ 20 21/* Variables for selection control. */ 22/* must not be deallocated */ 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{ 32 return inverse_translate(spk_sel_cons, 33 screen_glyph(spk_sel_cons, n), 0); 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{ 44 return !(p % size_row) || !((p + 2) % size_row); 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 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); 66 67 if (ps > pe) /* make sel_start <= sel_end */ 68 swap(ps, pe); 69 70 if (spk_sel_cons != vc_cons[fg_console].d) { 71 speakup_clear_selection(); 72 spk_sel_cons = vc_cons[fg_console].d; 73 dev_warn(tty->dev, 74 "Selection: mark console not the same as cut\n"); 75 return -EINVAL; 76 } 77 78 new_sel_start = ps; 79 new_sel_end = pe; 80 81 /* select to end of line if on trailing space */ 82 if (new_sel_end > new_sel_start && 83 !atedge(new_sel_end, vc->vc_size_row) && 84 ishardspace(sel_pos(new_sel_end))) { 85 for (pe = new_sel_end + 2; ; pe += 2) 86 if (!ishardspace(sel_pos(pe)) || 87 atedge(pe, vc->vc_size_row)) 88 break; 89 if (ishardspace(sel_pos(pe))) 90 new_sel_end = pe; 91 } 92 if ((new_sel_start == sel_start) && (new_sel_end == sel_end)) 93 return 0; /* no action required */ 94 95 sel_start = new_sel_start; 96 sel_end = new_sel_end; 97 /* Allocate a new buffer before freeing the old one ... */ 98 bp = kmalloc((sel_end - sel_start) / 2 + 1, GFP_ATOMIC); 99 if (!bp) { 100 speakup_clear_selection(); 101 return -ENOMEM; 102 } 103 kfree(sel_buffer); 104 sel_buffer = bp; 105 106 obp = bp; 107 for (i = sel_start; i <= sel_end; i += 2) { 108 *bp = sel_pos(i); 109 if (!ishardspace(*bp++)) 110 obp = bp; 111 if (!((i + 2) % vc->vc_size_row)) { 112 /* strip trailing blanks from line and add newline, 113 * unless non-space at end of line. 114 */ 115 if (obp != bp) { 116 bp = obp; 117 *bp++ = '\r'; 118 } 119 obp = bp; 120 } 121 } 122 sel_buffer_lth = bp - sel_buffer; 123 return 0; 124} 125 126struct speakup_paste_work { 127 struct work_struct work; 128 struct tty_struct *tty; 129}; 130 131static void __speakup_paste_selection(struct work_struct *work) 132{ 133 struct speakup_paste_work *spw = 134 container_of(work, struct speakup_paste_work, work); 135 struct tty_struct *tty = xchg(&spw->tty, NULL); 136 struct vc_data *vc = (struct vc_data *)tty->driver_data; 137 int pasted = 0, count; 138 struct tty_ldisc *ld; 139 DECLARE_WAITQUEUE(wait, current); 140 141 ld = tty_ldisc_ref(tty); 142 if (!ld) 143 goto tty_unref; 144 tty_buffer_lock_exclusive(&vc->port); 145 146 add_wait_queue(&vc->paste_wait, &wait); 147 while (sel_buffer && sel_buffer_lth > pasted) { 148 set_current_state(TASK_INTERRUPTIBLE); 149 if (tty_throttled(tty)) { 150 schedule(); 151 continue; 152 } 153 count = sel_buffer_lth - pasted; 154 count = tty_ldisc_receive_buf(ld, sel_buffer + pasted, NULL, 155 count); 156 pasted += count; 157 } 158 remove_wait_queue(&vc->paste_wait, &wait); 159 __set_current_state(TASK_RUNNING); 160 161 tty_buffer_unlock_exclusive(&vc->port); 162 tty_ldisc_deref(ld); 163tty_unref: 164 tty_kref_put(tty); 165} 166 167static struct speakup_paste_work speakup_paste_work = { 168 .work = __WORK_INITIALIZER(speakup_paste_work.work, 169 __speakup_paste_selection) 170}; 171 172int speakup_paste_selection(struct tty_struct *tty) 173{ 174 if (cmpxchg(&speakup_paste_work.tty, NULL, tty)) 175 return -EBUSY; 176 177 tty_kref_get(tty); 178 schedule_work_on(WORK_CPU_UNBOUND, &speakup_paste_work.work); 179 return 0; 180} 181 182void speakup_cancel_paste(void) 183{ 184 cancel_work_sync(&speakup_paste_work.work); 185 tty_kref_put(speakup_paste_work.tty); 186}