1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 00:07:36 +00:00

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).
This commit is contained in:
Daniel Bertalan 2021-05-24 12:01:59 +02:00 committed by Ali Mohammad Pur
parent ee24f2eb2d
commit 708f835477
4 changed files with 53 additions and 12 deletions

View file

@ -105,6 +105,10 @@ void Terminal::alter_mode(bool should_set, Parameters params, Intermediates inte
m_client.set_cursor_style(None); m_client.set_cursor_style(None);
} }
break; break;
case 2004:
dbgln_if(TERMINAL_DEBUG, "Setting bracketed mode enabled={}", should_set);
m_needs_bracketed_paste = should_set;
break;
default: default:
dbgln("Terminal::alter_mode: Unimplemented private mode {} (should_set={})", mode, should_set); dbgln("Terminal::alter_mode: Unimplemented private mode {} (should_set={})", mode, should_set);
break; break;

View file

@ -157,6 +157,11 @@ public:
Attribute attribute_at(const Position&) const; Attribute attribute_at(const Position&) const;
#endif #endif
bool needs_bracketed_paste() const
{
return m_needs_bracketed_paste;
};
protected: protected:
// ^EscapeSequenceExecutor // ^EscapeSequenceExecutor
virtual void emit_code_point(u32) override; virtual void emit_code_point(u32) override;
@ -336,6 +341,8 @@ protected:
CursorStyle m_cursor_style { BlinkingBlock }; CursorStyle m_cursor_style { BlinkingBlock };
CursorStyle m_saved_cursor_style { BlinkingBlock }; CursorStyle m_saved_cursor_style { BlinkingBlock };
bool m_needs_bracketed_paste { false };
Attribute m_current_attribute; Attribute m_current_attribute;
Attribute m_saved_attribute; Attribute m_saved_attribute;

View file

@ -748,17 +748,14 @@ void TerminalWidget::paste()
{ {
if (m_ptm_fd == -1) if (m_ptm_fd == -1)
return; return;
auto mime_type = GUI::Clipboard::the().mime_type(); auto mime_type = GUI::Clipboard::the().mime_type();
if (!mime_type.starts_with("text/")) if (!mime_type.starts_with("text/"))
return; return;
auto text = GUI::Clipboard::the().data(); auto text = GUI::Clipboard::the().data();
if (text.is_empty()) if (text.is_empty())
return; return;
int nwritten = write(m_ptm_fd, text.data(), text.size()); send_non_user_input(text);
if (nwritten < 0) {
perror("write");
VERIFY_NOT_REACHED();
}
} }
void TerminalWidget::copy() void TerminalWidget::copy()
@ -1091,20 +1088,21 @@ void TerminalWidget::drop_event(GUI::DropEvent& event)
if (event.mime_data().has_text()) { if (event.mime_data().has_text()) {
event.accept(); event.accept();
auto text = event.mime_data().text(); 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()) { } else if (event.mime_data().has_urls()) {
event.accept(); event.accept();
auto urls = event.mime_data().urls(); auto urls = event.mime_data().urls();
bool first = true; bool first = true;
for (auto& url : event.mime_data().urls()) { for (auto& url : event.mime_data().urls()) {
if (!first) { if (!first)
write(m_ptm_fd, " ", 1); send_non_user_input(" "sv.bytes());
first = false;
}
if (url.protocol() == "file") if (url.protocol() == "file")
write(m_ptm_fd, url.path().characters(), url.path().length()); send_non_user_input(url.path().bytes());
else 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); set_font(font);
resize(widget_size_for_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<u8> output;
output.ensure_capacity(leading_control_sequence.bytes().size() + bytes.size() + trailing_control_sequence.bytes().size());
// HACK: We don't have a `Vector<T>::unchecked_append(Span<T> 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();
}
}
} }

View file

@ -118,6 +118,8 @@ private:
void set_logical_focus(bool); void set_logical_focus(bool);
void send_non_user_input(const ReadonlyBytes&);
Gfx::IntRect glyph_rect(u16 row, u16 column); Gfx::IntRect glyph_rect(u16 row, u16 column);
Gfx::IntRect row_rect(u16 row); Gfx::IntRect row_rect(u16 row);