Commit 89101dc5 authored by Matan Ziv-Av's avatar Matan Ziv-Av Committed by Tomaz Canabrava
Browse files

Add partial support for kitty terminal graphics protocol

parent d4e1f4ac
......@@ -230,6 +230,8 @@ qt5_add_resources( konsoleprivate_SRCS ../desktop/konsole.qrc)
add_library(konsoleprivate ${konsoleprivate_SRCS})
generate_export_header(konsoleprivate BASE_NAME konsoleprivate)
find_package(ZLIB)
target_link_libraries(konsoleprivate
PUBLIC
konsoleprivate_core
......@@ -241,6 +243,7 @@ target_link_libraries(konsoleprivate
konsolecharacters
konsoledecoders
${konsole_LIBS}
ZLIB::ZLIB
)
set_target_properties(konsoleprivate PROPERTIES
......
......@@ -26,6 +26,9 @@
#include "keyboardtranslator/KeyboardTranslator.h"
#include "session/SessionController.h"
#include "terminalDisplay/TerminalDisplay.h"
#include "terminalDisplay/TerminalFonts.h"
#include <zlib.h>
using Konsole::Vt102Emulation;
......@@ -69,6 +72,10 @@ Vt102Emulation::Vt102Emulation()
QObject::connect(_sessionAttributesUpdateTimer, &QTimer::timeout, this, &Konsole::Vt102Emulation::updateSessionAttributes);
initTokenizer();
imageData = QByteArray();
imageId = 0;
savedKeys = QMap<char, qint64>();
tokenData = QByteArray();
}
Vt102Emulation::~Vt102Emulation() = default;
......@@ -243,6 +250,7 @@ void Vt102Emulation::resetTokenizer()
argc = 0;
argv[0] = 0;
argv[1] = 0;
tokenState = -1;
}
void Vt102Emulation::addDigit(int digit)
......@@ -341,6 +349,7 @@ void Vt102Emulation::initTokenizer()
#define esp( ) (p >= 4 && s[2] == SP )
#define epsp( ) (p >= 5 && s[3] == SP )
#define osc (tokenBufferPos >= 2 && tokenBuffer[1] == ']')
#define apc (tokenBufferPos >= 2 && tokenBuffer[1] == '_')
#define ces(C) (cc < 256 && (charClass[cc] & (C)) == (C))
#define dcs (p >= 2 && s[0] == ESC && s[1] == 'P')
......@@ -369,11 +378,11 @@ void Vt102Emulation::receiveChars(const QVector<uint> &chars)
// ignore control characters in the text part of osc (aka OSC) "ESC]"
// escape sequences; this matches what XTERM docs say
// Allow BEL and ESC here, it will either end the text or be removed later.
if (osc && cc != 0x1b && cc != 0x07) {
if ((osc || apc) && cc != 0x1b && cc != 0x07) {
continue;
}
if (!osc) {
if (!osc && !apc) {
// DEC HACK ALERT! Control Characters are allowed *within* esc sequences in VT100
// This means, they do neither a resetTokenizer() nor a pushToToken(). Some of them, do
// of course. Guess this originates from a weakly layered handling of the X-on
......@@ -479,11 +488,45 @@ void Vt102Emulation::receiveChars(const QVector<uint> &chars)
}
}
}
// Application Program Command
if (p > 2 && s[1] == '_') {
// <ESC> '_' ... <ESC> '\'
if (p > 3 && s[2] == 'G') {
if (tokenState == -1) {
tokenStateChange = ";";
tokenState = 0;
} else if (tokenState >= 0) {
if ((uint)tokenStateChange[tokenState] == s[p - 1]) {
tokenState++;
tokenPos = p;
if ((uint)tokenState == strlen(tokenStateChange)) {
tokenState = -2;
tokenData.clear();
}
continue;
}
} else if (tokenState == -2) {
if (p - tokenPos == 4) {
tokenData.append(QByteArray::fromBase64(QString::fromUcs4(&tokenBuffer[tokenPos], 4).toLocal8Bit()));
tokenBufferPos -= 4;
continue;
}
}
}
if (s[p - 1] == 0x07 || (s[p - 2] == ESC && s[p - 1] == '\\')) {
if (s[2] == 'G') {
// Graphics command
processGraphicsToken(p);
resetTokenizer();
continue;
}
}
}
/* clang-format off */
// <ESC> ']' ...
if (osc ) { continue; }
if (apc ) { continue; }
if (p >= 3 && s[1] == '[') { // parsing a CSI sequence
if (lec(3,2,'?')) { continue; }
if (lec(3,2,'=')) { continue; }
......@@ -750,6 +793,7 @@ void Vt102Emulation::processSessionAttributeRequest(int tokenSize)
p->X = 0;
p->Y = 0;
p->opacity = 1.0;
p->scrolling = true;
p->col = _currentScreen->getCursorX();
p->row = _currentScreen->getCursorY();
p->pixmap = QPixmap::fromImage(image);
......@@ -1230,6 +1274,220 @@ void Vt102Emulation::processToken(int token, int p, int q)
/* clang-format on */
}
void Vt102Emulation::processGraphicsToken(int tokenSize)
{
QString value = QString::fromUcs4(&tokenBuffer[3], tokenSize - 4);
QStringList list;
QImage *image = NULL;
int dataPos = value.indexOf(QLatin1Char(';'));
if (dataPos == -1) {
dataPos = value.size() - 1;
}
if (dataPos > 1024) {
reportDecodingError();
return;
}
list = value.mid(0, dataPos).split(QLatin1Char(','));
QMap<char, qint64> keys; // Keys may be signed or unsigned 32 bit integers
if (savedKeys.empty()) {
keys['a'] = 't';
keys['t'] = 'd';
keys['q'] = 0;
keys['m'] = 0;
keys['f'] = 32;
keys['i'] = 0;
keys['o'] = 0;
keys['X'] = 0;
keys['Y'] = 0;
keys['x'] = 0;
keys['y'] = 0;
keys['z'] = 0;
keys['C'] = 0;
keys['c'] = 0;
keys['r'] = 0;
keys['A'] = 255;
keys['I'] = 0;
keys['d'] = 'a';
keys['p'] = -1;
} else {
keys = QMap<char, qint64>(savedKeys);
}
for (int i = 0; i < list.size(); i++) {
if (list.at(i).at(1).toLatin1() != '=') {
reportDecodingError();
return;
}
if (list.at(i).at(2).isNumber() || list.at(i).at(2).toLatin1() == '-')
keys[list.at(i).at(0).toLatin1()] = list.at(i).mid(2).toInt();
else
keys[list.at(i).at(0).toLatin1()] = list.at(i).at(2).toLatin1();
}
if (keys['a'] == 't' || keys['a'] == 'T' || keys['a'] == 'q') {
if (keys['q'] < 2 && keys['t'] != 'd') {
QString params = QStringLiteral("i=") + QString::number(keys['i']);
QString error = QStringLiteral("ENOTSUPPORTED:");
sendGraphicsReply(params, error);
return;
}
if (keys['I']) {
keys['i'] = _currentScreen->currentTerminalDisplay()->getFreeGraphicsImageId();
}
if (imageId != keys['i']) {
imageId = keys['i'];
imageData = QByteArray();
}
imageData.append(tokenData);
tokenData.clear();
imageData.append(QByteArray::fromBase64(value.mid(dataPos + 1).toLocal8Bit()));
if (keys['m'] == 0) {
QByteArray *out = new QByteArray();
if (keys['o'] == 'z') {
int alloc;
unsigned char *data = (unsigned char *)imageData.constData();
z_stream stream;
[[maybe_unused]] int ret;
if (keys['f'] == 24 || keys['f'] == 32) {
int bpp = keys['f'] / 8;
alloc = bpp * keys['s'] * keys['v'];
} else {
alloc = 8 * 1024 * 1024;
}
out->resize(alloc);
/* allocate inflate state */
stream.zalloc = (alloc_func)Z_NULL;
stream.zfree = (free_func)Z_NULL;
stream.opaque = (voidpf)Z_NULL;
stream.avail_in = imageData.size(); // size of input
stream.next_in = (Bytef *)data; // input char array
stream.avail_out = out->size(); // size of output
stream.next_out = (Bytef *)out->constData(); // output char array
ret = inflateInit(&stream);
inflate(&stream, Z_NO_FLUSH);
inflateEnd(&stream);
if (keys['f'] != 24 && keys['f'] != 32) {
imageData.clear();
imageData.append(*out);
}
} else {
out = NULL;
}
if (keys['f'] == 24 || keys['f'] == 32) {
enum QImage::Format format = keys['f'] == 24 ? QImage::Format_RGB888 : QImage::Format_RGBA8888;
if (!out) {
out = new QByteArray(imageData.constData(), imageData.size());
}
image = new QImage((unsigned char *)out->constData(), 0 + keys['s'], 0 + keys['v'], 0 + keys['s'] * keys['f'] / 8, format);
} else {
image = new QImage();
if (!out) {
out = &imageData;
}
image->loadFromData(*out);
}
if (keys['a'] == 'q') {
QString params = QStringLiteral("i=") + QString::number(keys['i']);
sendGraphicsReply(params, QString());
} else {
if (keys['i'])
_currentScreen->currentTerminalDisplay()->setGraphicsImage(keys['i'], image);
if (keys['q'] == 0 && keys['a'] == 't') {
QString params = QStringLiteral("i=") + QString::number(keys['i']);
if (keys['I'])
params = params + QStringLiteral(",I=") + QString::number(keys['I']);
sendGraphicsReply(params, QString());
}
}
imageId = 0;
imageData = QByteArray();
savedKeys = QMap<char, qint64>();
} else {
if (savedKeys.empty()) {
savedKeys = QMap<char, qint64>(keys);
savedKeys.remove('m');
}
}
}
if (keys['a'] == 'p' || (keys['a'] == 'T' && keys['m'] == 0)) {
TerminalGraphicsPlacement_t *p;
if (keys['i'])
image = _currentScreen->currentTerminalDisplay()->getGraphicsImage(keys['i']);
if (image) {
p = new TerminalGraphicsPlacement_t();
p->id = keys['i'];
p->pid = keys['p'];
p->z = keys['z'];
p->X = keys['X'];
p->Y = keys['Y'];
p->opacity = (qreal)keys['A'];
p->scrolling = true;
p->col = _currentScreen->getCursorX();
p->row = _currentScreen->getCursorY();
p->pixmap = QPixmap::fromImage(*image);
if (keys['x'] || keys['y'] || keys['w'] || keys['h']) {
int w = keys['w'] ? keys['w'] : p->pixmap.width() - keys['x'];
int h = keys['h'] ? keys['h'] : p->pixmap.height() - keys['y'];
p->pixmap = p->pixmap.copy(keys['x'], keys['y'], w, h);
}
if (keys['c'] && keys['r']) {
p->pixmap = p->pixmap.scaled(keys['c'] * _currentScreen->currentTerminalDisplay()->terminalFont()->fontWidth(),
keys['r'] * _currentScreen->currentTerminalDisplay()->terminalFont()->fontHeight());
}
int rows = (p->Y + p->pixmap.height()) / _currentScreen->currentTerminalDisplay()->terminalFont()->fontHeight();
int cols = (p->X + p->pixmap.width() - 1) / _currentScreen->currentTerminalDisplay()->terminalFont()->fontWidth();
p->cols = cols;
p->rows = rows;
int needScroll = p->row + p->rows - _currentScreen->bottomMargin();
if (needScroll < 0)
needScroll = 0;
if (needScroll > 0)
_currentScreen->scrollUp(needScroll);
p->row -= needScroll;
_currentScreen->currentTerminalDisplay()->addPlacement(p);
if (keys['C'] == 0) {
_currentScreen->cursorDown(rows - needScroll);
_currentScreen->cursorRight(cols);
}
if (keys['q'] == 0 && keys['i']) {
QString params = QStringLiteral("i=") + QString::number(keys['i']);
if (keys['I'])
params = params + QStringLiteral(",I=") + QString::number(keys['I']);
if (keys['p'] >= 0)
params = params + QStringLiteral(",p=") + QString::number(keys['p']);
sendGraphicsReply(params, QString());
}
} else {
if (keys['q'] < 2) {
QString params = QStringLiteral("i=") + QString::number(keys['i']);
sendGraphicsReply(params, QStringLiteral("ENOENT:No such image"));
}
}
}
if (keys['a'] == 'd') {
int action = keys['d'] | 0x20;
int id = keys['i'];
int pid = keys['p'];
int x = keys['x'];
int y = keys['y'];
if (action == 'n') {
} else if (action == 'c') {
action = 'p';
x = _currentScreen->getCursorX();
y = _currentScreen->getCursorY();
}
_currentScreen->currentTerminalDisplay()->delPlacements(action, id, pid, x, y, keys['z']);
}
}
void Vt102Emulation::clearScreenAndSetColumns(int columnCount)
{
setImageSize(_currentScreen->getLines(), columnCount);
......@@ -1243,6 +1501,12 @@ void Vt102Emulation::sendString(const QByteArray &s)
Q_EMIT sendData(s);
}
void Vt102Emulation::sendGraphicsReply(QString params, QString error)
{
sendString(
(QStringLiteral("\033_G") + params + QStringLiteral(";") + (error.isEmpty() ? QStringLiteral("OK") : error) + QStringLiteral("\033\\")).toLatin1());
}
void Vt102Emulation::reportCursorPosition()
{
char tmp[30];
......
......@@ -10,6 +10,7 @@
// Qt
#include <QHash>
#include <QMap>
#include <QPair>
#include <QVector>
......@@ -128,18 +129,29 @@ private:
int argv[MAXARGS];
int argc;
void initTokenizer();
// State machine for escape sequences containing large amount of data
int tokenState;
const char *tokenStateChange;
int tokenPos;
QByteArray tokenData;
// Set of flags for each of the ASCII characters which indicates
// what category they fall into (printable character, control, digit etc.)
// for the purposes of decoding terminal output
int charClass[256];
QByteArray imageData;
quint32 imageId;
QMap<char, qint64> savedKeys;
void reportDecodingError();
void processToken(int code, int p, int q);
void processSessionAttributeRequest(int tokenSize);
void processChecksumRequest(int argc, int argv[]);
void processGraphicsToken(int tokenSize);
void sendGraphicsReply(QString params, QString error);
void reportTerminalType();
void reportTertiaryAttributes();
void reportSecondaryAttributes();
......
......@@ -2952,6 +2952,39 @@ int TerminalDisplay::selectionState() const
return _actSel;
}
QImage *TerminalDisplay::getGraphicsImage(int id)
{
if (_graphicsImages.count(id)) {
return _graphicsImages[id];
}
return NULL;
}
void TerminalDisplay::setGraphicsImage(int id, QImage *pixmap)
{
_graphicsImages[id] = pixmap;
}
std::map<int, QImage *>::iterator TerminalDisplay::getGraphicsImagesBegin()
{
return _graphicsImages.begin();
}
std::map<int, QImage *>::iterator TerminalDisplay::getGraphicsImagesEnd()
{
return _graphicsImages.end();
}
int TerminalDisplay::getFreeGraphicsImageId()
{
int i = 1;
while (1) {
if (!_graphicsImages.count(i))
return i;
i++;
}
}
void TerminalDisplay::addPlacement(TerminalGraphicsPlacement_t *p)
{
int i;
......
......@@ -384,6 +384,13 @@ public:
// Used to show/hide the message widget
void updateReadOnlyState(bool readonly);
// For kitty graphics protocol - image cache
QImage *getGraphicsImage(int id);
void setGraphicsImage(int id, QImage *pixmap);
std::map<int, QImage *>::iterator getGraphicsImagesBegin();
std::map<int, QImage *>::iterator getGraphicsImagesEnd();
int getFreeGraphicsImageId();
void addPlacement(TerminalGraphicsPlacement_t *p);
TerminalGraphicsPlacement_t *getGraphicsPlacement(int i);
void scrollUpVisiblePlacements(int n);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment