From f596b12a04a2e4afbc51486d44a1ffc6d6f0b4aa Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sat, 9 May 2020 16:16:16 +0200 Subject: [PATCH] LibVT+Terminal: Support hyperlinks in the terminal :^) We now support basic hyperlinking in the terminal with ;8;;URL Links are opened via LaunchServer on Ctrl+LeftMouse. --- Applications/Terminal/Makefile | 2 +- Applications/Terminal/main.cpp | 7 ++++++- DevTools/HackStudio/Makefile | 2 +- Libraries/LibVT/Terminal.cpp | 20 +++++++++++++++++++- Libraries/LibVT/Terminal.h | 7 +++++++ Libraries/LibVT/TerminalWidget.cpp | 27 +++++++++++++++++++++++++-- Libraries/LibVT/TerminalWidget.h | 3 +++ 7 files changed, 62 insertions(+), 6 deletions(-) diff --git a/Applications/Terminal/Makefile b/Applications/Terminal/Makefile index 5bcea44d5d..b3deb0b3bf 100644 --- a/Applications/Terminal/Makefile +++ b/Applications/Terminal/Makefile @@ -3,6 +3,6 @@ OBJS = \ PROGRAM = Terminal -LIB_DEPS = GUI Gfx VT IPC Protocol Core +LIB_DEPS = GUI Gfx VT Desktop IPC Protocol Core include ../../Makefile.common diff --git a/Applications/Terminal/main.cpp b/Applications/Terminal/main.cpp index b4e0451bde..507dd605f4 100644 --- a/Applications/Terminal/main.cpp +++ b/Applications/Terminal/main.cpp @@ -190,7 +190,7 @@ int main(int argc, char** argv) GUI::Application app(argc, argv); - if (pledge("stdio tty rpath accept cpath wpath shared_buffer proc exec", nullptr) < 0) { + if (pledge("stdio tty rpath accept cpath wpath shared_buffer proc exec unix", nullptr) < 0) { perror("pledge"); return 1; } @@ -313,6 +313,11 @@ int main(int argc, char** argv) return 1; } + if (unveil("/tmp/portal/launch", "rw") < 0) { + perror("unveil"); + return 1; + } + if (unveil(config->file_name().characters(), "rwc")) { perror("unveil"); return 1; diff --git a/DevTools/HackStudio/Makefile b/DevTools/HackStudio/Makefile index 3b3f2f1955..89bd7b610c 100644 --- a/DevTools/HackStudio/Makefile +++ b/DevTools/HackStudio/Makefile @@ -19,6 +19,6 @@ OBJS = \ PROGRAM = HackStudio -LIB_DEPS = GUI Web TextCodec VT Protocol Markdown Gfx IPC Thread Pthread Core JS Debug +LIB_DEPS = GUI Web TextCodec VT Desktop Protocol Markdown Gfx IPC Thread Pthread Core JS Debug include ../../Makefile.common diff --git a/Libraries/LibVT/Terminal.cpp b/Libraries/LibVT/Terminal.cpp index 5da3ccc751..ae6679a392 100644 --- a/Libraries/LibVT/Terminal.cpp +++ b/Libraries/LibVT/Terminal.cpp @@ -61,7 +61,8 @@ void Terminal::Line::set_length(u16 new_length) memset(new_characters, ' ', new_length); if (characters && attributes) { memcpy(new_characters, characters, min(m_length, new_length)); - memcpy(new_attributes, attributes, min(m_length, new_length) * sizeof(Attribute)); + for (size_t i = 0; i < min(m_length, new_length); ++i) + new_attributes[i] = attributes[i]; } delete[] characters; delete[] attributes; @@ -605,6 +606,11 @@ void Terminal::execute_xterm_command() case 2: m_client.set_window_title(params[1]); break; + case 8: + m_current_attribute.href = params[2]; + // FIXME: Respect the provided ID + m_current_attribute.href_id = String::format("%u", m_next_href_id++); + break; default: unimplemented_xterm_escape(); break; @@ -1076,4 +1082,16 @@ void Terminal::execute_hashtag(u8 hashtag) } } +Attribute Terminal::attribute_at(const Position& position) const +{ + if (!position.is_valid()) + return {}; + if (position.row() >= static_cast(m_lines.size())) + return {}; + auto& line = this->line(position.row()); + if (position.column() >= line.m_length) + return {}; + return line.attributes[position.column()]; +} + } diff --git a/Libraries/LibVT/Terminal.h b/Libraries/LibVT/Terminal.h index 66387f1459..b73f9cf2f8 100644 --- a/Libraries/LibVT/Terminal.h +++ b/Libraries/LibVT/Terminal.h @@ -60,6 +60,9 @@ struct Attribute { u8 foreground_color; u8 background_color; + String href; + String href_id; + enum Flags : u8 { NoAttributes = 0x00, Bold = 0x01, @@ -137,6 +140,8 @@ public: void inject_string(const StringView&); + Attribute attribute_at(const Position&) const; + private: typedef Vector ParamVector; @@ -204,6 +209,8 @@ private: Attribute m_current_attribute; + u32 m_next_href_id { 0 }; + void execute_escape_sequence(u8 final); void execute_xterm_command(); void execute_hashtag(u8); diff --git a/Libraries/LibVT/TerminalWidget.cpp b/Libraries/LibVT/TerminalWidget.cpp index a3ade54cff..1a4e812e57 100644 --- a/Libraries/LibVT/TerminalWidget.cpp +++ b/Libraries/LibVT/TerminalWidget.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -371,7 +372,11 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event) if (!has_only_one_background_color || should_reverse_fill_for_cursor_or_selection) { painter.clear_rect(cell_rect, lookup_color(should_reverse_fill_for_cursor_or_selection ? attribute.foreground_color : attribute.background_color).with_alpha(m_opacity)); } - if (attribute.flags & VT::Attribute::Underline) + + bool should_paint_underline = attribute.flags & VT::Attribute::Underline + || (!m_hovered_href.is_empty() && m_hovered_href_id == attribute.href_id); + + if (should_paint_underline) painter.draw_line(cell_rect.bottom_left(), cell_rect.bottom_right(), lookup_color(should_reverse_fill_for_cursor_or_selection ? attribute.background_color : attribute.foreground_color)); } @@ -586,6 +591,15 @@ void TerminalWidget::copy() void TerminalWidget::mousedown_event(GUI::MouseEvent& event) { + if (event.modifiers() == Mod_Ctrl && event.button() == GUI::MouseButton::Left) { + auto attribute = m_terminal.attribute_at(buffer_position_at(event.position())); + if (!attribute.href.is_empty()) { + dbg() << "Open URL: _" << attribute.href << "_"; + Desktop::Launcher::open(attribute.href); + } + return; + } + if (event.button() == GUI::MouseButton::Left) { if (m_triple_click_timer.is_valid() && m_triple_click_timer.elapsed() < 250) { int start_column = 0; @@ -609,11 +623,20 @@ void TerminalWidget::mousedown_event(GUI::MouseEvent& event) void TerminalWidget::mousemove_event(GUI::MouseEvent& event) { + auto position = buffer_position_at(event.position()); + + auto attribute = m_terminal.attribute_at(position); + if (attribute.href_id != m_hovered_href_id) { + m_hovered_href_id = attribute.href_id; + m_hovered_href = attribute.href; + update(); + } + if (!(event.buttons() & GUI::MouseButton::Left)) return; auto old_selection_end = m_selection_end; - m_selection_end = buffer_position_at(event.position()); + m_selection_end = position; if (old_selection_end != m_selection_end) update(); } diff --git a/Libraries/LibVT/TerminalWidget.h b/Libraries/LibVT/TerminalWidget.h index 6eb99a74a4..ca244aca1e 100644 --- a/Libraries/LibVT/TerminalWidget.h +++ b/Libraries/LibVT/TerminalWidget.h @@ -129,6 +129,9 @@ private: VT::Position m_selection_start; VT::Position m_selection_end; + String m_hovered_href; + String m_hovered_href_id; + bool m_should_beep { false }; bool m_belling { false }; bool m_alt_key_held { false };