linux/drivers/staging/speakup/selection.c
<<
>>
Prefs
   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/device.h> /* for dev_warn */
   6#include <linux/selection.h>
   7#include <linux/workqueue.h>
   8#include <linux/tty.h>
   9#include <linux/tty_flip.h>
  10#include <linux/atomic.h>
  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
  18unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */
  19
  20/* Variables for selection control. */
  21/* must not be deallocated */
  22struct vc_data *spk_sel_cons;
  23/* cleared by clear_selection */
  24static int sel_start = -1;
  25static int sel_end;
  26static int sel_buffer_lth;
  27static char *sel_buffer;
  28
  29static unsigned char sel_pos(int n)
  30{
  31        return inverse_translate(spk_sel_cons,
  32                screen_glyph(spk_sel_cons, n), 0);
  33}
  34
  35void 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? */
  41static int atedge(const int p, int size_row)
  42{
  43        return !(p % size_row) || !((p + 2) % size_row);
  44}
  45
  46/* constrain v such that v <= u */
  47static unsigned short limit(const unsigned short v, const unsigned short u)
  48{
  49        return (v > u) ? u : v;
  50}
  51
  52int 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
  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);
  65
  66        if (ps > pe) {
  67                /* make sel_start <= sel_end */
  68                int tmp = ps;
  69
  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;
  77                dev_warn(tty->dev,
  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) {
 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,
 117                         * unless non-space at end of line.
 118                         */
 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
 130struct speakup_paste_work {
 131        struct work_struct work;
 132        struct tty_struct *tty;
 133};
 134
 135static void __speakup_paste_selection(struct work_struct *work)
 136{
 137        struct speakup_paste_work *spw =
 138                container_of(work, struct speakup_paste_work, work);
 139        struct tty_struct *tty = xchg(&spw->tty, NULL);
 140        struct vc_data *vc = (struct vc_data *) tty->driver_data;
 141        int pasted = 0, count;
 142        struct tty_ldisc *ld;
 143        DECLARE_WAITQUEUE(wait, current);
 144
 145        ld = tty_ldisc_ref(tty);
 146        if (!ld)
 147                goto tty_unref;
 148        tty_buffer_lock_exclusive(&vc->port);
 149
 150        add_wait_queue(&vc->paste_wait, &wait);
 151        while (sel_buffer && sel_buffer_lth > pasted) {
 152                set_current_state(TASK_INTERRUPTIBLE);
 153                if (test_bit(TTY_THROTTLED, &tty->flags)) {
 154                        schedule();
 155                        continue;
 156                }
 157                count = sel_buffer_lth - pasted;
 158                count = tty_ldisc_receive_buf(ld, sel_buffer + pasted, NULL,
 159                                              count);
 160                pasted += count;
 161        }
 162        remove_wait_queue(&vc->paste_wait, &wait);
 163        __set_current_state(TASK_RUNNING);
 164
 165        tty_buffer_unlock_exclusive(&vc->port);
 166        tty_ldisc_deref(ld);
 167tty_unref:
 168        tty_kref_put(tty);
 169}
 170
 171static struct speakup_paste_work speakup_paste_work = {
 172        .work = __WORK_INITIALIZER(speakup_paste_work.work,
 173                                   __speakup_paste_selection)
 174};
 175
 176int 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);
 183        return 0;
 184}
 185
 186void speakup_cancel_paste(void)
 187{
 188        cancel_work_sync(&speakup_paste_work.work);
 189        tty_kref_put(speakup_paste_work.tty);
 190}
 191