diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c index 8967c3a0d916..545ca051635a 100644 --- a/drivers/tty/vt/selection.c +++ b/drivers/tty/vt/selection.c @@ -42,21 +42,20 @@ static struct vc_selection { char *buffer; unsigned int buf_len; volatile int start; /* cleared by clear_selection */ - int end; + int end; /* Note: this points to the last char, not to after it. */ + bool mouse_at_e; + unsigned short prev_x; } vc_sel = { .lock = __MUTEX_INITIALIZER(vc_sel.lock), .start = -1, }; +static bool unicode; +static unsigned int size_row; + /* clear_selection, highlight and highlight_pointer can be called from interrupt (via scrollback/front) */ -/* set reverse video on characters s-e of console with selection. */ -static inline void highlight(const int s, const int e) -{ - invert_screen(vc_sel.cons, s, e-s+2, true); -} - /* use complementary color to show the pointer */ static inline void highlight_pointer(const int where) { @@ -64,7 +63,7 @@ static inline void highlight_pointer(const int where) } static u32 -sel_pos(int n, bool unicode) +sel_pos(int n) { if (unicode) return screen_glyph_unicode(vc_sel.cons, n / 2); @@ -72,6 +71,61 @@ sel_pos(int n, bool unicode) false); } +static int last_char_pos(int line_end, int limit) +{ + int pos = line_end; + while ((pos >= limit) && is_space_on_vt(sel_pos(pos))) + pos -= 2; + return pos; +} + +/* set reverse video on characters s-e of console with selection. */ +static void highlight(const int s, const int e) +{ + bool mouse_at_e = (vc_sel.start != -1) && vc_sel.mouse_at_e; + int bol = s; + int eol = rounddown(s, size_row) + size_row - 2; + int pos; + + if (mouse_at_e) { + int prev_e = s - 2; /* For the + 2 in vc_do_selection. */ + int low_mouse_beg = rounddown(prev_e, size_row); + int high_mouse_beg = rounddown(e, size_row); + if (low_mouse_beg != high_mouse_beg) { + /* Unhighlight any trailing space on the previous line + * (moving down), or rehighlight it (moving up). */ + int low_mouse_end = low_mouse_beg + size_row - 2; + + pos = last_char_pos(low_mouse_end, + max(vc_sel.start, low_mouse_beg)) + 2; + if (prev_e >= pos) + invert_screen(vc_sel.cons, pos, + prev_e - pos + 2, true); + } + } + + while (eol < e) { + if ((pos = last_char_pos(eol, bol)) >= bol) + invert_screen(vc_sel.cons, bol, pos - bol + 2, true); + bol = eol + 2; + eol += size_row; + } + /* Last line: Firstly, are we (un)highlighting the entire selection? */ + if ((vc_sel.start == -1) || ((s == vc_sel.start && e == vc_sel.end)) || + /* .... or trailing space at the end of the selection? */ + (eol >= vc_sel.end)) + /* YES: so highlight the entire last line up to E. */ + invert_screen(vc_sel.cons, bol, e - bol + 2, true); + else { + /* NO: Don't highlight the trailing spaces. */ + pos = last_char_pos(eol, bol); + if (pos > e) + pos = e; + if (pos >= bol) + invert_screen(vc_sel.cons, bol, pos - bol + 2, true); + } +} + /** * clear_selection - remove current selection * @@ -186,7 +240,7 @@ int set_selection_user(const struct tiocl_selection __user *sel, return set_selection_kernel(&v, tty); } -static int vc_selection_store_chars(struct vc_data *vc, bool unicode) +static int vc_selection_store_chars(void) { char *bp, *obp; unsigned int i; @@ -205,16 +259,17 @@ static int vc_selection_store_chars(struct vc_data *vc, bool unicode) obp = bp; for (i = vc_sel.start; i <= vc_sel.end; i += 2) { - u32 c = sel_pos(i, unicode); + u32 c = sel_pos(i); if (unicode) bp += store_utf8(c, bp); else *bp++ = c; if (!is_space_on_vt(c)) obp = bp; - if (!((i + 2) % vc->vc_size_row)) { + if (!((i + 2) % size_row) && + (i + 2) < vc_sel.end) { /* strip trailing blanks from line and add newline, - unless non-space at end of line. */ + unless non-space at end of line or on last line. */ if (obp != bp) { bp = obp; *bp++ = '\r'; @@ -227,11 +282,10 @@ static int vc_selection_store_chars(struct vc_data *vc, bool unicode) return 0; } -static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps, - int pe) +static int vc_do_selection(unsigned short mode, int ps, int pe) { int new_sel_start, new_sel_end, spc; - bool unicode = vt_do_kdgkbmode(fg_console) == K_UNICODE; + int pe_line_start, term_space; switch (mode) { case TIOCL_SELCHAR: /* character-by-character selection */ @@ -239,30 +293,37 @@ static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps, new_sel_end = pe; break; case TIOCL_SELWORD: /* word-by-word selection */ - spc = is_space_on_vt(sel_pos(ps, unicode)); + spc = is_space_on_vt(sel_pos(ps)); for (new_sel_start = ps; ; ps -= 2) { - if ((spc && !is_space_on_vt(sel_pos(ps, unicode))) || - (!spc && !inword(sel_pos(ps, unicode)))) + if ((spc && !is_space_on_vt(sel_pos(ps))) || + (!spc && !inword(sel_pos(ps)))) break; new_sel_start = ps; - if (!(ps % vc->vc_size_row)) + if (!(ps % size_row)) break; } - spc = is_space_on_vt(sel_pos(pe, unicode)); + spc = is_space_on_vt(sel_pos(pe)); + if (spc) term_space = pe; for (new_sel_end = pe; ; pe += 2) { - if ((spc && !is_space_on_vt(sel_pos(pe, unicode))) || - (!spc && !inword(sel_pos(pe, unicode)))) + if ((spc && !is_space_on_vt(sel_pos(pe))) || + (!spc && !inword(sel_pos(pe)))) break; new_sel_end = pe; - if (!((pe + 2) % vc->vc_size_row)) + if (!((pe + 2) % size_row)) break; } + /* Don't highlight trailing space after the mouse on the last + * line. */ + if (spc && + !((pe + 2) % size_row)) + new_sel_end = last_char_pos(term_space, ps); break; case TIOCL_SELLINE: /* line-by-line selection */ - new_sel_start = rounddown(ps, vc->vc_size_row); - new_sel_end = rounddown(pe, vc->vc_size_row) + - vc->vc_size_row - 2; + new_sel_start = rounddown(ps, size_row); + pe_line_start = rounddown(pe, size_row); + new_sel_end = last_char_pos(pe_line_start + size_row - 2, + ps); /* Can be before BOL. */ break; case TIOCL_SELPOINTER: highlight_pointer(pe); @@ -274,17 +335,6 @@ static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps, /* remove the pointer */ highlight_pointer(-1); - /* select to end of line if on trailing space */ - if (new_sel_end > new_sel_start && - !atedge(new_sel_end, vc->vc_size_row) && - is_space_on_vt(sel_pos(new_sel_end, unicode))) { - for (pe = new_sel_end + 2; ; pe += 2) - if (!is_space_on_vt(sel_pos(pe, unicode)) || - atedge(pe, vc->vc_size_row)) - break; - if (is_space_on_vt(sel_pos(pe, unicode))) - new_sel_end = pe; - } if (vc_sel.start == -1) /* no current selection */ highlight(new_sel_start, new_sel_end); else if (new_sel_start == vc_sel.start) @@ -303,22 +353,32 @@ static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps, else /* contract from left */ highlight(vc_sel.start, new_sel_start - 2); } - else /* some other case; start selection from scratch */ + else /* The mouse has been moved through the starting point or we + * have word or line selection. */ { - clear_selection(); + /* Restore vc_sel.mouse_at_e to its previous value to + * unhighlight the previous selection. */ + vc_sel.mouse_at_e = !vc_sel.mouse_at_e; + highlight(vc_sel.start, vc_sel.end); + vc_sel.mouse_at_e = !vc_sel.mouse_at_e; + + vc_sel.start = new_sel_start; + vc_sel.end = new_sel_end; highlight(new_sel_start, new_sel_end); } vc_sel.start = new_sel_start; vc_sel.end = new_sel_end; - return vc_selection_store_chars(vc, unicode); + return vc_selection_store_chars(); } static int vc_selection(struct vc_data *vc, struct tiocl_selection *v, struct tty_struct *tty) { int ps, pe; + int res; + size_row = vc->vc_size_row; poke_blanked_console(); if (v->sel_mode == TIOCL_SELCLEAR) { @@ -327,6 +387,20 @@ static int vc_selection(struct vc_data *vc, struct tiocl_selection *v, return 0; } + /* Heuristically correct any strange values from GPM. When the mouse + * is dragged off the left hand edge, GPM reports it as being at the + * end of the previous line. When it is dragged off the bottom edge, + * it is reported as being at the end of the last line. */ + if ((v->xe == vc->vc_cols) && (vc_sel.start != -1)) { + if (v->ye == vc->vc_rows) { + if (vc_sel.prev_x <= (vc->vc_cols - 20)) + v->xe = vc_sel.prev_x; + } else if (vc_sel.prev_x <= (vc->vc_cols >> 1)) { + v->xe = 1; + v->ye++; + } + } + v->xs = min_t(u16, v->xs - 1, vc->vc_cols - 1); v->ys = min_t(u16, v->ys - 1, vc->vc_rows - 1); v->xe = min_t(u16, v->xe - 1, vc->vc_cols - 1); @@ -338,17 +412,27 @@ static int vc_selection(struct vc_data *vc, struct tiocl_selection *v, return 0; } - ps = v->ys * vc->vc_size_row + (v->xs << 1); - pe = v->ye * vc->vc_size_row + (v->xe << 1); - if (ps > pe) /* make vc_sel.start <= vc_sel.end */ + vc_sel.mouse_at_e = true; + ps = v->ys * size_row + (v->xs << 1); + pe = v->ye * size_row + (v->xe << 1); + if (ps > pe) { /* make vc_sel.start <= vc_sel.end */ swap(ps, pe); + vc_sel.mouse_at_e = false; + } if (vc_sel.cons != vc) { clear_selection(); vc_sel.cons = vc; } - return vc_do_selection(vc, v->sel_mode, ps, pe); + unicode = vt_do_kdgkbmode(fg_console) == K_UNICODE; + + res = vc_do_selection(v->sel_mode, ps, pe); + if ((vc_sel.start != -1) && + ((v->sel_mode == TIOCL_SELCHAR) || (v->sel_mode == TIOCL_SELWORD) || + (v->sel_mode == TIOCL_SELLINE))) + vc_sel.prev_x = v->xe + 1; /* Convert back to 1-based. */ + return res; } int set_selection_kernel(struct tiocl_selection *v, struct tty_struct *tty)