Hello, Gentoo.

As a long time user of the Linux console, two things about the GPM mouse
utility get on my nerves.

The first is that on pasting a selection, a carriage return is appended to
the line (or the last line).  This means that, for example, on copying a
bash command from one tty to another, there is no opportunity to edit it
before it gets run.

The second is that while selecting, the highlighting on the screen is
done crudely to the end of each line in the selection, rather than to the
end of the text in that line.

I have a fix for these, now.  The pertinent source file is
drivers/tty/vt/selection.c in the kernel.  The patch is in the attached
file 6.8.1-SELECTION.20241215.diff.  To use this patch (but see below,
first), extract the patch to your home directory, cd to
/usr/src/linux-6.6.62-gentoo (or later version of the kernel), then issue
the command:

    $ patch --dry-run -p1 < ~/6.8.1-SELECTION.20241215.diff

..  This is just a dry run which checks the patch applies cleanly rather
than actually applying it.  If everything is OK, apply it for real with:

    $ patch -p1 < ~/6.8.1-SELECTION.20241215.diff

..  If, on the other hand, you get an error saying Hunk #7 failed, you've
probably already applied my earlier patch to enable scrolling on the
console, which included a patch for problem 1 above.  You will need to
undo that patch before applying the main one.  Extract the attached patch
6.6.13-TRIPLE.20240123.diff, and apply it reversed as follows:

    $ patch -p1 -R < ~/6.6.13-TRIPLE.20240123.diff

..  Then apply the main patch, as above.

Having done that, rebuild your kernel, and install it to the /boot
partition in the normal (for you) way.  Reboot and enjoy!

The usual reservations apply, of course.  There's nothing malicious in
the patches, but if it breaks for you, I'll be sorry and will handle bug
reports.  Nothing more.

-- 
Alan Mackenzie (Nuremberg, Germany).

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)
diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c
index 8967c3a0d916..f40ebbfb87de 100644
--- a/drivers/tty/vt/selection.c
+++ b/drivers/tty/vt/selection.c
@@ -217,7 +217,8 @@ static int vc_selection_store_chars(struct vc_data *vc, 
bool unicode)
                           unless non-space at end of line. */
                        if (obp != bp) {
                                bp = obp;
-                               *bp++ = '\r';
+                               if ((i + 2) < vc_sel.end) /* Don't add \r to 
the last line. */
+                                       *bp++ = '\r';
                        }
                        obp = bp;
                }
@@ -263,6 +264,11 @@ static int vc_do_selection(struct vc_data *vc, unsigned 
short mode, int ps,
                new_sel_start = rounddown(ps, vc->vc_size_row);
                new_sel_end = rounddown(pe, vc->vc_size_row) +
                        vc->vc_size_row - 2;
+               while ((new_sel_end > pe)
+                      && (is_space_on_vt (sel_pos (new_sel_end, unicode))))
+                       new_sel_end -= 2;
+               if (!((new_sel_end) % vc->vc_size_row))
+                       new_sel_end += 2;
                break;
        case TIOCL_SELPOINTER:
                highlight_pointer(pe);

Reply via email to