Commit faceafcc authored by Tomaz  Canabrava's avatar Tomaz Canabrava

Extract URL from a Escape Sequence and provide a HotSpot for activation

This path adds a new feature for konsole, in the form of URL Escaped
Sequences. It allows programs to embbed URL's on texts much like
the anchor tag in html does

There's a allowed list of possible schemas for links, by default
it only accepts http://, https:// and file:// but the user can
add more if he wants.

The maximum amount of URL's accepted is 200, to prevent OOM

History is taken into account while scrooling
parent 30109167
......@@ -113,18 +113,22 @@ set(konsoleprivate_SRCS ${windowadaptors_SRCS}
CompositeWidgetFocusWatcher.cpp
CopyInputDialog.cpp
Emulation.cpp
EscapeSequenceUrlExtractor.cpp
ExtendedCharTable.cpp
FontDialog.cpp
hsluv.c
HTMLDecoder.cpp
HistorySizeDialog.cpp
KeyBindingEditor.cpp
LabelsAligner.cpp
LineBlockCharacters.cpp
NullProcessInfo.cpp
NullProcessInfo.cpp
PlainTextDecoder.cpp
PrintOptions.cpp
PrintOptions.cpp
ProcessInfo.cpp
ProcessInfo.cpp
Pty.cpp
Pty.cpp
RenameTabDialog.cpp
SSHProcessInfo.cpp
......@@ -138,7 +142,10 @@ set(konsoleprivate_SRCS ${windowadaptors_SRCS}
ViewManager.cpp
ViewProperties.cpp
Vt102Emulation.cpp
WindowSystemInfo.cpp
ZModemDialog.cpp
filterHotSpots/EscapeSequenceUrlFilter.cpp
filterHotSpots/EscapeSequenceUrlFilterHotSpot.cpp
filterHotSpots/FileFilter.cpp
filterHotSpots/FileFilterHotspot.cpp
filterHotSpots/Filter.cpp
......@@ -162,9 +169,13 @@ set(konsoleprivate_SRCS ${windowadaptors_SRCS}
history/compact/CompactHistoryScroll.cpp
history/compact/CompactHistoryType.cpp
widgets/DetachableTabBar.cpp
widgets/DetachableTabBar.cpp
widgets/EditProfileDialog.cpp
widgets/HistorySizeWidget.cpp
widgets/HistorySizeWidget.cpp
widgets/IncrementalSearchBar.cpp
widgets/IncrementalSearchBar.cpp
widgets/RenameTabWidget.cpp
widgets/RenameTabWidget.cpp
widgets/TabTitleFormatButton.cpp
widgets/TerminalDisplay.cpp
......
/*
This file is part of Konsole, KDE's terminal.
Copyright 2007-2008 by Robert Knight <robertknight@gmail.com>
Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
*/
#include "EscapeSequenceUrlExtractor.h"
namespace Konsole {
EscapeSequenceUrlExtractor::EscapeSequenceUrlExtractor()
{
}
void Konsole::EscapeSequenceUrlExtractor::setScreen(Konsole::Screen* screen)
{
_screen = screen;
clear();
}
bool EscapeSequenceUrlExtractor::reading() const
{
return _reading;
}
void EscapeSequenceUrlExtractor::beginUrlInput()
{
_reading = true;
}
void EscapeSequenceUrlExtractor::appendUrlText(QChar c)
{
if (!reading()) {
return;
}
if (_currentUrl.text.isEmpty()) {
// We need to on getCursorX because we want the position of the
// last printed character, not the cursor.
const int realCcolumn = _screen->getCursorY() + _screen->getHistLines();
_currentUrl.begin = Coordinate{realCcolumn, _screen->getCursorX() - 1};
}
_currentUrl.text += c;
}
void EscapeSequenceUrlExtractor::setUrl(const QString& url)
{
_currentUrl.url = url;
for (const auto &schema : _allowedUriSchemas) {
if (url.startsWith(schema)) {
return;
}
}
abortUrlInput();
}
void EscapeSequenceUrlExtractor::abortUrlInput()
{
_reading = false;
_currentUrl = ExtractedUrl{};
_ignoreNextUrlInput = true;
}
void EscapeSequenceUrlExtractor::endUrlInput()
{
Q_ASSERT(reading());
_reading = false;
const int realCcolumn = _screen->getCursorY() + _screen->getHistLines();
const auto currentPos = Coordinate{realCcolumn, _screen->getCursorX()};
_currentUrl.end = currentPos;
_history.append(_currentUrl);
_currentUrl = ExtractedUrl{};
}
void EscapeSequenceUrlExtractor::clear()
{
_history.clear();
}
void EscapeSequenceUrlExtractor::setAllowedLinkSchema(const QStringList& schema)
{
_allowedUriSchemas = schema;
}
void EscapeSequenceUrlExtractor::historyLinesRemoved(int lines)
{
for (auto &url : _history) {
url.begin.row -= lines;
url.end.row -= lines;
}
_history.erase(
std::remove_if(std::begin(_history), std::end(_history), [](const ExtractedUrl& url) {
const bool toRemove = url.begin.row < 0;
return toRemove;
}),
std::end(_history));
}
QVector<ExtractedUrl> EscapeSequenceUrlExtractor::history() const
{
return _history;
}
void Konsole::EscapeSequenceUrlExtractor::toggleUrlInput()
{
if (_ignoreNextUrlInput) {
_ignoreNextUrlInput = false;
return;
}
if (_reading) {
endUrlInput();
} else {
beginUrlInput();
}
}
}
#ifndef ESCAPE_SEQUENCE_URL_EXTRACTOR_H
#define ESCAPE_SEQUENCE_URL_EXTRACTOR_H
/*
This file is part of Konsole, KDE's terminal.
Copyright 2007-2008 by Robert Knight <robertknight@gmail.com>
Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
*/
#include <QObject>
#include <QPointer>
#include "Screen.h"
namespace Konsole {
/* Like QPoint, but with Row / Col
* easier to read than x / y
*/
struct Coordinate {
int row;
int col;
};
/* Represents a URL on the visual part of konsole, that has been escaped.
* like a html url tag with a text value.
*/
struct ExtractedUrl {
QString url;
QString text;
Coordinate begin;
Coordinate end;
};
/* Stored in Screen, but used in V10Emulation to
* store extracted URL's. Perhaps this should be a Model?
*/
class EscapeSequenceUrlExtractor {
private:
/* Tell us if we are currently reading or not a URL. */
bool _reading = false;
/* If we abort reading a URL input we enter in a invalid state,
* and we need to ignore the next toggle.
*/
bool _ignoreNextUrlInput = false;
/* The url / text pair being extracted currently */
ExtractedUrl _currentUrl;
/* The maximum size of url to prevent a bomb
* that will take over the history file.
* TODO: make it configurable.
*/
const int _maximumUrlHistory = 200;
/* All of the extracted URL's. */
QVector<ExtractedUrl> _history;
/* The URI schema format that's accepted */
QStringList _allowedUriSchemas;
/* Pointer to the Screen, that actually holds the text data. */
Screen *_screen;
public:
/* This needs to have access to the Session
* calculate the row / col of the current URL.
*/
EscapeSequenceUrlExtractor();
/* This is a list of URI schemas that are going to be supported, separated by semicollon.
* like https://;ile://
*/
void setAllowedLinkSchema(const QStringList& allowedSchemas);
void setScreen(Screen *screen);
/* If we are parsing a URL */
bool reading() const;
/* We found an URL, starting to parse */
void beginUrlInput();
/* We received the end byte to finish the Url. */
void endUrlInput();
/* We are not saving this URL, it's bogus. */
void abortUrlInput();
/* The URL is parsed at once, but not the text. We received
* one character per time untill we hit the end byte. */
void appendUrlText(QChar c);
/* The URL is parsed at once, store it at once. */
void setUrl(const QString& url);
/* All of the parsedURL's, used by TerminalDisplay to paint them
* on screen. */
QVector<ExtractedUrl> history() const;
/* Clear all the URL's, this is triggered when the Screen is cleared. */
void clear();
/* Iterates trough all the URL's and remove the ones that are currently
* out of bounds because we removed lines in the History
*/
void historyLinesRemoved(int lines);
/* starts / stops URL Processing */
void toggleUrlInput();
};
}
#endif
......@@ -33,6 +33,7 @@
#include "history/HistoryScrollNone.h"
#include "ExtendedCharTable.h"
#include "profile/Profile.h"
#include "EscapeSequenceUrlExtractor.h"
using namespace Konsole;
......@@ -82,8 +83,10 @@ Screen::Screen(int lines, int columns):
_effectiveBackground(CharacterColor()),
_effectiveRendition(DEFAULT_RENDITION),
_lastPos(-1),
_lastDrawnChar(0)
_lastDrawnChar(0),
_escapeSequenceUrlExtractor(new EscapeSequenceUrlExtractor())
{
_escapeSequenceUrlExtractor->setScreen(this);
_lineProperties.resize(_lines + 1);
for (int i = 0; i < _lines + 1; i++) {
_lineProperties[i] = LINE_DEFAULT;
......@@ -828,6 +831,7 @@ void Screen::displayCharacter(uint c)
w--;
}
_cuX = newCursorX;
_escapeSequenceUrlExtractor->appendUrlText(QChar(c));
}
int Screen::scrolledLines() const
......@@ -1465,9 +1469,8 @@ void Screen::addHistLine()
{
// add line to history buffer
// we have to take care about scrolling, too...
const int oldHistLines = _history->getLines();
if (hasScroll()) {
const int oldHistLines = _history->getLines();
_history->addCellsVector(_screenLines[0]);
_history->addLine((_lineProperties[0] & LINE_WRAPPED) != 0);
......@@ -1517,6 +1520,12 @@ void Screen::addHistLine()
}
}
}
// We removed a line, we need to verify if we need to remove a URL.
const int newHistLines = _history->getLines();
if (oldHistLines == newHistLines) {
_escapeSequenceUrlExtractor->historyLinesRemoved(1);
}
}
int Screen::getHistLines() const
......@@ -1561,3 +1570,9 @@ void Screen::fillWithDefaultChar(Character* dest, int count)
dest[i] = Screen::DefaultChar;
}
}
Konsole::EscapeSequenceUrlExtractor * Konsole::Screen::urlExtractor() const
{
return _escapeSequenceUrlExtractor;
}
......@@ -47,6 +47,7 @@ class TerminalCharacterDecoder;
class TerminalDisplay;
class HistoryType;
class HistoryScroll;
class EscapeSequenceUrlExtractor;
/**
\brief An image of characters with associated attributes.
......@@ -99,6 +100,8 @@ public:
Screen(const Screen &) = delete;
Screen &operator=(const Screen &) = delete;
EscapeSequenceUrlExtractor *urlExtractor() const;
// VT100/2 Operations
// Cursor Movement
......@@ -735,6 +738,8 @@ private:
// used in REP (repeating char)
quint32 _lastDrawnChar;
EscapeSequenceUrlExtractor *_escapeSequenceUrlExtractor;
void toggleUrlInput();
};
Q_DECLARE_OPERATORS_FOR_FLAGS(Screen::DecodingOptions)
......
......@@ -39,6 +39,8 @@
#include "keyboardtranslator/KeyboardTranslator.h"
#include "session/SessionController.h"
#include "widgets/TerminalDisplay.h"
#include "EscapeSequenceUrlExtractor.h"
#include "Screen.h"
using Konsole::Vt102Emulation;
......@@ -64,6 +66,10 @@ unsigned short Konsole::vt100_graphics[32] = {
0x252c, 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00b7
};
enum XTERM_EXTENDED {
URL_LINK = '8'
};
Vt102Emulation::Vt102Emulation() :
Emulation(),
_currentModes(TerminalState()),
......@@ -396,7 +402,17 @@ void Vt102Emulation::receiveChar(uint cc)
// Operating System Command
if (p > 2 && s[1] == ']') {
// <ESC> ']' ... <ESC> '\'
if (s[p-2] == ESC && s[p-1] == '\\') { processSessionAttributeRequest(p-1); resetTokenizer(); return; }
if (s[p-2] == ESC && s[p-1] == '\\') {
// This runs two times per link, the first prepares the link to be read,
// the second finalizes it.
if (s[2] == XTERM_EXTENDED::URL_LINK) {
// printf '\e]8;;http://example.com\e\\This is a link\e]8;;\e\\\n'
_currentScreen->urlExtractor()->toggleUrlInput();
}
processSessionAttributeRequest(p-1);
resetTokenizer();
return;
}
// <ESC> ']' ... <ESC> + one character for reprocessing
if (s[p-2] == ESC) { processSessionAttributeRequest(p-1); resetTokenizer(); receiveChar(cc); return; }
// <ESC> ']' ... <BEL>
......@@ -509,7 +525,12 @@ void Vt102Emulation::processSessionAttributeRequest(int tokenSize)
// skip ';'
++i;
const QString value = QString::fromUcs4(&tokenBuffer[i], tokenSize - i);
QString value = QString::fromUcs4(&tokenBuffer[i], tokenSize - i);
if (_currentScreen->urlExtractor()->reading()) {
value.remove(0,1);
_currentScreen->urlExtractor()->setUrl(value);
return;
}
if (value == QLatin1String("?")) {
emit sessionAttributeRequest(attribute);
......@@ -552,7 +573,8 @@ void Vt102Emulation::processToken(int token, int p, int q)
{
switch (token)
{
case token_chr( ) : _currentScreen->displayCharacter (p ); break; //UTF16
case token_chr( ) :
_currentScreen->displayCharacter (p ); break; //UTF16
// 127 DEL : ignored on input
......
......@@ -25,6 +25,8 @@
// Qt
#include <QHash>
#include <QPair>
#include <QVector>
// Konsole
#include "Emulation.h"
......@@ -189,6 +191,7 @@ private:
bool _reportFocusEvents;
};
}
#endif // VT102EMULATION_H
set(konsole_colorscheme_SRCS
hsluv.c
ColorScheme.cpp
ColorSchemeManager.cpp
ColorSchemeWallpaper.cpp
......
/*
Copyright 2007-2008 by Robert Knight <robertknight@gmail.com>
Copyright 2020 by Tomaz Canabrava <tcanabrava@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
*/
#include "EscapeSequenceUrlFilter.h"
#include "session/Session.h"
#include "widgets/TerminalDisplay.h"
#include "EscapeSequenceUrlExtractor.h"
#include "EscapeSequenceUrlFilterHotSpot.h"
using namespace Konsole;
EscapeSequenceUrlFilter::EscapeSequenceUrlFilter(Session* session, TerminalDisplay *window)
{
_session = session;
_window = window;
}
void EscapeSequenceUrlFilter::process()
{
if (!_window->screenWindow() && _window->screenWindow()->screen()) {
return;
}
auto *sWindow = _window->screenWindow();
const auto urls = sWindow->screen()->urlExtractor()->history();
for (const auto &escapedUrl : urls) {
if (escapedUrl.begin.row < sWindow->currentLine() || escapedUrl.end.row > sWindow->currentLine() + sWindow->windowLines()) {
continue;
}
const int beginRow = escapedUrl.begin.row - sWindow->currentLine();
const int endRow = escapedUrl.end.row - sWindow->currentLine();
QSharedPointer<HotSpot> spot(
// TODO:
// This uses Column / Row while everything else uses Row/Column.
// Move everything else to QPoint begin / QPoint End.
new EscapeSequenceUrlHotSpot(beginRow, escapedUrl.begin.col,
endRow, escapedUrl.end.col,
escapedUrl.text,
escapedUrl.url
)
);
addHotSpot(spot);
}
}
/*
Copyright 2007-2008 by Robert Knight <robertknight@gmail.com>
Copyright 2020 by Tomaz Canabrava <tcanabrava@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
*/
#ifndef ESCAPE_SEQUENCE_URL_FILTER
#define ESCAPE_SEQUENCE_URL_FILTER
#include "Filter.h"
#include <QPointer>
namespace Konsole {
class Session;
class TerminalDisplay;
/* This filter is different from the Url filter as there's no
* URL's in the screen. Vt102Emulation will store a vector of
* URL/Text, we need to match if this is in the screen. For that we need a pointer
* for the Vt102Emulation or at least the data structure that holds the information
* so we can create the hotspots.
*/
class EscapeSequenceUrlFilter : public Filter
{
public:
EscapeSequenceUrlFilter(Session *session, TerminalDisplay *display);
void process() override;
private:
QPointer<Session> _session;
QPointer<TerminalDisplay> _window;
};
}
#endif
/*
Copyright 2007-2008 by Robert Knight <robertknight@gmail.com>
Copyright 2020 by Tomaz Canabrava <tcanabrava@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.