vt: bracketed paste support
authorNicolas Pitre <npitre@baylibre.com>
Tue, 20 May 2025 17:16:43 +0000 (13:16 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 21 May 2025 11:41:03 +0000 (13:41 +0200)
This is comprised of 3 aspects:

- Take note of when applications advertise bracketed paste support via
  "\e[?2004h" and "\e[?2004l".

- Insert bracketed paste markers ("\e[200~" and "\e[201~") around pasted
  content in paste_selection() when bracketed paste is active.

- Add TIOCL_GETBRACKETEDPASTE to return bracketed paste status so user
  space daemons implementing cut-and-paste functionality (e.g. gpm,
  BRLTTY) may know when to insert bracketed paste markers.

Link: https://en.wikipedia.org/wiki/Bracketed-paste
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
Reviewed-by: Jiri Slaby <jirislaby@kernel.org>
Link: https://lore.kernel.org/r/20250520171851.1219676-2-nico@fluxnic.net
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/vt/selection.c
drivers/tty/vt/vt.c
include/linux/console_struct.h
include/uapi/linux/tiocl.h

index 791e2f1f7c0b65b5258e6b8f47a7a95576316710..24b0a53e5a796cb3cc61aa1b188c5b50cd8f7f92 100644 (file)
@@ -403,6 +403,12 @@ int paste_selection(struct tty_struct *tty)
        DECLARE_WAITQUEUE(wait, current);
        int ret = 0;
 
+       bool bp = vc->vc_bracketed_paste;
+       static const char bracketed_paste_start[] = "\033[200~";
+       static const char bracketed_paste_end[]   = "\033[201~";
+       const char *bps = bp ? bracketed_paste_start : NULL;
+       const char *bpe = bp ? bracketed_paste_end : NULL;
+
        console_lock();
        poke_blanked_console();
        console_unlock();
@@ -414,7 +420,7 @@ int paste_selection(struct tty_struct *tty)
 
        add_wait_queue(&vc->paste_wait, &wait);
        mutex_lock(&vc_sel.lock);
-       while (vc_sel.buffer && vc_sel.buf_len > pasted) {
+       while (vc_sel.buffer && (vc_sel.buf_len > pasted || bpe)) {
                set_current_state(TASK_INTERRUPTIBLE);
                if (signal_pending(current)) {
                        ret = -EINTR;
@@ -427,10 +433,27 @@ int paste_selection(struct tty_struct *tty)
                        continue;
                }
                __set_current_state(TASK_RUNNING);
+
+               if (bps) {
+                       bps += tty_ldisc_receive_buf(ld, bps, NULL, strlen(bps));
+                       if (*bps != '\0')
+                               continue;
+                       bps = NULL;
+               }
+
                count = vc_sel.buf_len - pasted;
-               count = tty_ldisc_receive_buf(ld, vc_sel.buffer + pasted, NULL,
-                                             count);
-               pasted += count;
+               if (count) {
+                       pasted += tty_ldisc_receive_buf(ld, vc_sel.buffer + pasted,
+                                                       NULL, count);
+                       if (vc_sel.buf_len > pasted)
+                               continue;
+               }
+
+               if (bpe) {
+                       bpe += tty_ldisc_receive_buf(ld, bpe, NULL, strlen(bpe));
+                       if (*bpe == '\0')
+                               bpe = NULL;
+               }
        }
        mutex_unlock(&vc_sel.lock);
        remove_wait_queue(&vc->paste_wait, &wait);
index efb7614541666cdeeb2b226af0e14b737f6f24a1..ed39d9cb4432cb48f5148deb0e3bc957e7a753de 100644 (file)
@@ -1870,6 +1870,14 @@ int mouse_reporting(void)
        return vc_cons[fg_console].d->vc_report_mouse;
 }
 
+/* invoked via ioctl(TIOCLINUX) */
+static int get_bracketed_paste(struct tty_struct *tty)
+{
+       struct vc_data *vc = tty->driver_data;
+
+       return vc->vc_bracketed_paste;
+}
+
 enum {
        CSI_DEC_hl_CURSOR_KEYS  = 1,    /* CKM: cursor keys send ^[Ox/^[[x */
        CSI_DEC_hl_132_COLUMNS  = 3,    /* COLM: 80/132 mode switch */
@@ -1880,6 +1888,7 @@ enum {
        CSI_DEC_hl_MOUSE_X10    = 9,
        CSI_DEC_hl_SHOW_CURSOR  = 25,   /* TCEM */
        CSI_DEC_hl_MOUSE_VT200  = 1000,
+       CSI_DEC_hl_BRACKETED_PASTE = 2004,
 };
 
 /* console_lock is held */
@@ -1932,6 +1941,9 @@ static void csi_DEC_hl(struct vc_data *vc, bool on_off)
                case CSI_DEC_hl_MOUSE_VT200:
                        vc->vc_report_mouse = on_off ? 2 : 0;
                        break;
+               case CSI_DEC_hl_BRACKETED_PASTE:
+                       vc->vc_bracketed_paste = on_off;
+                       break;
                }
 }
 
@@ -2157,6 +2169,7 @@ static void reset_terminal(struct vc_data *vc, int do_clear)
        vc->state.charset       = 0;
        vc->vc_need_wrap        = 0;
        vc->vc_report_mouse     = 0;
+       vc->vc_bracketed_paste  = 0;
        vc->vc_utf              = default_utf8;
        vc->vc_utf_count        = 0;
 
@@ -3483,6 +3496,8 @@ int tioclinux(struct tty_struct *tty, unsigned long arg)
                break;
        case TIOCL_BLANKEDSCREEN:
                return console_blanked;
+       case TIOCL_GETBRACKETEDPASTE:
+               return get_bracketed_paste(tty);
        default:
                return -EINVAL;
        }
index 20f564e9855234308afc6768d67635435d6c5bed..59b4fec5f2548cb1814f94604bda81f1eb25e0b6 100644 (file)
@@ -145,6 +145,7 @@ struct vc_data {
        unsigned int    vc_need_wrap    : 1;
        unsigned int    vc_can_do_color : 1;
        unsigned int    vc_report_mouse : 2;
+       unsigned int    vc_bracketed_paste : 1;
        unsigned char   vc_utf          : 1;    /* Unicode UTF-8 encoding */
        unsigned char   vc_utf_count;
                 int    vc_utf_char;
index b32acc229024b39cd2badda4112e9051745aeed5..88faba506c3d9e48ee8c08a5746112b9dd98f757 100644 (file)
@@ -36,5 +36,6 @@ struct tiocl_selection {
 #define TIOCL_BLANKSCREEN      14      /* keep screen blank even if a key is pressed */
 #define TIOCL_BLANKEDSCREEN    15      /* return which vt was blanked */
 #define TIOCL_GETKMSGREDIRECT  17      /* get the vt the kernel messages are restricted to */
+#define TIOCL_GETBRACKETEDPASTE        18      /* get whether paste may be bracketed */
 
 #endif /* _LINUX_TIOCL_H */