1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 06:47:35 +00:00

LibVT+Terminal: Support hyperlinks in the terminal :^)

We now support basic hyperlinking in the terminal with <OSC>;8;;URL<ST>
Links are opened via LaunchServer on Ctrl+LeftMouse.
This commit is contained in:
Andreas Kling 2020-05-09 16:16:16 +02:00
parent 427863f275
commit f596b12a04
7 changed files with 62 additions and 6 deletions

View file

@ -3,6 +3,6 @@ OBJS = \
PROGRAM = Terminal PROGRAM = Terminal
LIB_DEPS = GUI Gfx VT IPC Protocol Core LIB_DEPS = GUI Gfx VT Desktop IPC Protocol Core
include ../../Makefile.common include ../../Makefile.common

View file

@ -190,7 +190,7 @@ int main(int argc, char** argv)
GUI::Application app(argc, 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"); perror("pledge");
return 1; return 1;
} }
@ -313,6 +313,11 @@ int main(int argc, char** argv)
return 1; return 1;
} }
if (unveil("/tmp/portal/launch", "rw") < 0) {
perror("unveil");
return 1;
}
if (unveil(config->file_name().characters(), "rwc")) { if (unveil(config->file_name().characters(), "rwc")) {
perror("unveil"); perror("unveil");
return 1; return 1;

View file

@ -19,6 +19,6 @@ OBJS = \
PROGRAM = HackStudio 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 include ../../Makefile.common

View file

@ -61,7 +61,8 @@ void Terminal::Line::set_length(u16 new_length)
memset(new_characters, ' ', new_length); memset(new_characters, ' ', new_length);
if (characters && attributes) { if (characters && attributes) {
memcpy(new_characters, characters, min(m_length, new_length)); 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[] characters;
delete[] attributes; delete[] attributes;
@ -605,6 +606,11 @@ void Terminal::execute_xterm_command()
case 2: case 2:
m_client.set_window_title(params[1]); m_client.set_window_title(params[1]);
break; 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: default:
unimplemented_xterm_escape(); unimplemented_xterm_escape();
break; 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<int>(m_lines.size()))
return {};
auto& line = this->line(position.row());
if (position.column() >= line.m_length)
return {};
return line.attributes[position.column()];
}
} }

View file

@ -60,6 +60,9 @@ struct Attribute {
u8 foreground_color; u8 foreground_color;
u8 background_color; u8 background_color;
String href;
String href_id;
enum Flags : u8 { enum Flags : u8 {
NoAttributes = 0x00, NoAttributes = 0x00,
Bold = 0x01, Bold = 0x01,
@ -137,6 +140,8 @@ public:
void inject_string(const StringView&); void inject_string(const StringView&);
Attribute attribute_at(const Position&) const;
private: private:
typedef Vector<unsigned, 4> ParamVector; typedef Vector<unsigned, 4> ParamVector;
@ -204,6 +209,8 @@ private:
Attribute m_current_attribute; Attribute m_current_attribute;
u32 m_next_href_id { 0 };
void execute_escape_sequence(u8 final); void execute_escape_sequence(u8 final);
void execute_xterm_command(); void execute_xterm_command();
void execute_hashtag(u8); void execute_hashtag(u8);

View file

@ -32,6 +32,7 @@
#include <AK/Utf8View.h> #include <AK/Utf8View.h>
#include <Kernel/KeyCode.h> #include <Kernel/KeyCode.h>
#include <LibCore/MimeData.h> #include <LibCore/MimeData.h>
#include <LibDesktop/Launcher.h>
#include <LibGUI/Action.h> #include <LibGUI/Action.h>
#include <LibGUI/Application.h> #include <LibGUI/Application.h>
#include <LibGUI/Clipboard.h> #include <LibGUI/Clipboard.h>
@ -371,7 +372,11 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
if (!has_only_one_background_color || should_reverse_fill_for_cursor_or_selection) { 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)); 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)); 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) 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 (event.button() == GUI::MouseButton::Left) {
if (m_triple_click_timer.is_valid() && m_triple_click_timer.elapsed() < 250) { if (m_triple_click_timer.is_valid() && m_triple_click_timer.elapsed() < 250) {
int start_column = 0; int start_column = 0;
@ -609,11 +623,20 @@ void TerminalWidget::mousedown_event(GUI::MouseEvent& event)
void TerminalWidget::mousemove_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)) if (!(event.buttons() & GUI::MouseButton::Left))
return; return;
auto old_selection_end = m_selection_end; 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) if (old_selection_end != m_selection_end)
update(); update();
} }

View file

@ -129,6 +129,9 @@ private:
VT::Position m_selection_start; VT::Position m_selection_start;
VT::Position m_selection_end; VT::Position m_selection_end;
String m_hovered_href;
String m_hovered_href_id;
bool m_should_beep { false }; bool m_should_beep { false };
bool m_belling { false }; bool m_belling { false };
bool m_alt_key_held { false }; bool m_alt_key_held { false };