From 708f8354771cc6a87082fbce06ff1f76ca6904cd Mon Sep 17 00:00:00 2001 From: Daniel Bertalan Date: Mon, 24 May 2021 12:01:59 +0200 Subject: [PATCH] LibVT: Implement Bracketed Paste Mode This mode allow us to escape any data that was not directly typed by the user. `vim` currently uses this. If we implement it in the shell, we could prevent newlines from being injected into the shell by pasting text or dragging files into it (see #7276). --- Userland/Libraries/LibVT/Terminal.cpp | 4 ++ Userland/Libraries/LibVT/Terminal.h | 7 +++ Userland/Libraries/LibVT/TerminalWidget.cpp | 52 ++++++++++++++++----- Userland/Libraries/LibVT/TerminalWidget.h | 2 + 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/Userland/Libraries/LibVT/Terminal.cpp b/Userland/Libraries/LibVT/Terminal.cpp index 477cc6e4db..0e1bd262b8 100644 --- a/Userland/Libraries/LibVT/Terminal.cpp +++ b/Userland/Libraries/LibVT/Terminal.cpp @@ -105,6 +105,10 @@ void Terminal::alter_mode(bool should_set, Parameters params, Intermediates inte m_client.set_cursor_style(None); } break; + case 2004: + dbgln_if(TERMINAL_DEBUG, "Setting bracketed mode enabled={}", should_set); + m_needs_bracketed_paste = should_set; + break; default: dbgln("Terminal::alter_mode: Unimplemented private mode {} (should_set={})", mode, should_set); break; diff --git a/Userland/Libraries/LibVT/Terminal.h b/Userland/Libraries/LibVT/Terminal.h index e5a596c523..2605299bbd 100644 --- a/Userland/Libraries/LibVT/Terminal.h +++ b/Userland/Libraries/LibVT/Terminal.h @@ -157,6 +157,11 @@ public: Attribute attribute_at(const Position&) const; #endif + bool needs_bracketed_paste() const + { + return m_needs_bracketed_paste; + }; + protected: // ^EscapeSequenceExecutor virtual void emit_code_point(u32) override; @@ -336,6 +341,8 @@ protected: CursorStyle m_cursor_style { BlinkingBlock }; CursorStyle m_saved_cursor_style { BlinkingBlock }; + bool m_needs_bracketed_paste { false }; + Attribute m_current_attribute; Attribute m_saved_attribute; diff --git a/Userland/Libraries/LibVT/TerminalWidget.cpp b/Userland/Libraries/LibVT/TerminalWidget.cpp index 432ff28df2..cc93514625 100644 --- a/Userland/Libraries/LibVT/TerminalWidget.cpp +++ b/Userland/Libraries/LibVT/TerminalWidget.cpp @@ -748,17 +748,14 @@ void TerminalWidget::paste() { if (m_ptm_fd == -1) return; + auto mime_type = GUI::Clipboard::the().mime_type(); if (!mime_type.starts_with("text/")) return; auto text = GUI::Clipboard::the().data(); if (text.is_empty()) return; - int nwritten = write(m_ptm_fd, text.data(), text.size()); - if (nwritten < 0) { - perror("write"); - VERIFY_NOT_REACHED(); - } + send_non_user_input(text); } void TerminalWidget::copy() @@ -1091,20 +1088,21 @@ void TerminalWidget::drop_event(GUI::DropEvent& event) if (event.mime_data().has_text()) { event.accept(); auto text = event.mime_data().text(); - write(m_ptm_fd, text.characters(), text.length()); + send_non_user_input(text.bytes()); } else if (event.mime_data().has_urls()) { event.accept(); auto urls = event.mime_data().urls(); bool first = true; for (auto& url : event.mime_data().urls()) { - if (!first) { - write(m_ptm_fd, " ", 1); - first = false; - } + if (!first) + send_non_user_input(" "sv.bytes()); + if (url.protocol() == "file") - write(m_ptm_fd, url.path().characters(), url.path().length()); + send_non_user_input(url.path().bytes()); else - write(m_ptm_fd, url.to_string().characters(), url.to_string().length()); + send_non_user_input(url.to_string().bytes()); + + first = false; } } } @@ -1155,4 +1153,34 @@ void TerminalWidget::set_font_and_resize_to_fit(const Gfx::Font& font) set_font(font); resize(widget_size_for_font(font)); } + +// Used for sending data that was not directly typed by the user. +// This basically wraps the code that handles sending the escape sequence in bracketed paste mode. +void TerminalWidget::send_non_user_input(const ReadonlyBytes& bytes) +{ + constexpr StringView leading_control_sequence = "\e[200~"; + constexpr StringView trailing_control_sequence = "\e[201~"; + + int nwritten; + if (m_terminal.needs_bracketed_paste()) { + // We do not call write() separately for the control sequences and the data, + // because that would present a race condition where another process could inject data + // to prematurely terminate the escape. Could probably be solved by file locking. + Vector output; + output.ensure_capacity(leading_control_sequence.bytes().size() + bytes.size() + trailing_control_sequence.bytes().size()); + + // HACK: We don't have a `Vector::unchecked_append(Span const&)` yet :^( + output.append(leading_control_sequence.bytes().data(), leading_control_sequence.bytes().size()); + output.append(bytes.data(), bytes.size()); + output.append(trailing_control_sequence.bytes().data(), trailing_control_sequence.bytes().size()); + nwritten = write(m_ptm_fd, output.data(), output.size()); + } else { + nwritten = write(m_ptm_fd, bytes.data(), bytes.size()); + } + if (nwritten < 0) { + perror("write"); + VERIFY_NOT_REACHED(); + } +} + } diff --git a/Userland/Libraries/LibVT/TerminalWidget.h b/Userland/Libraries/LibVT/TerminalWidget.h index 8e1e2fc790..7d658969b4 100644 --- a/Userland/Libraries/LibVT/TerminalWidget.h +++ b/Userland/Libraries/LibVT/TerminalWidget.h @@ -118,6 +118,8 @@ private: void set_logical_focus(bool); + void send_non_user_input(const ReadonlyBytes&); + Gfx::IntRect glyph_rect(u16 row, u16 column); Gfx::IntRect row_rect(u16 row);