├── .clang-format ├── .gitignore ├── LICENSE ├── README.md ├── src ├── qvtchar.cpp ├── qvtchar.h ├── qvtcharformat.cpp ├── qvtcharformat.h ├── qvterminal.cpp ├── qvterminal.h ├── qvterminal.pri ├── qvtlayout.cpp ├── qvtlayout.h ├── qvtline.cpp ├── qvtline.h └── vt │ ├── vt.cpp │ ├── vt.h │ ├── vt100.cpp │ └── vt100.h └── test ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h └── qvtest.pro /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: true 7 | AlignConsecutiveAssignments: false 8 | # AlignConsecutiveBitFields : true 9 | AlignConsecutiveDeclarations: false 10 | AlignEscapedNewlines: Right 11 | AlignOperands: Align 12 | AlignTrailingComments: true 13 | AllowAllArgumentsOnNextLine: true 14 | AllowAllConstructorInitializersOnNextLine: true 15 | AllowAllParametersOfDeclarationOnNextLine: false 16 | AllowShortBlocksOnASingleLine: Never 17 | AllowShortCaseLabelsOnASingleLine: false 18 | AllowShortFunctionsOnASingleLine: None 19 | AllowShortLambdasOnASingleLine: None 20 | AllowShortIfStatementsOnASingleLine: Never 21 | AllowShortEnumsOnASingleLine: false 22 | AllowShortLoopsOnASingleLine: false 23 | AlwaysBreakAfterDefinitionReturnType: None 24 | AlwaysBreakAfterReturnType: None 25 | AlwaysBreakBeforeMultilineStrings: false 26 | AlwaysBreakTemplateDeclarations: true 27 | BinPackArguments: false 28 | BinPackParameters: false 29 | BraceWrapping: 30 | AfterCaseLabel: true 31 | AfterClass: true 32 | AfterControlStatement: true 33 | AfterEnum: true 34 | AfterFunction: true 35 | AfterNamespace: false 36 | AfterObjCDeclaration: false 37 | AfterStruct: true 38 | AfterUnion: true 39 | AfterExternBlock: false 40 | BeforeCatch: false 41 | BeforeElse: true 42 | IndentBraces: false 43 | SplitEmptyFunction: true 44 | SplitEmptyRecord: true 45 | SplitEmptyNamespace: true 46 | BreakBeforeBinaryOperators: NonAssignment 47 | BreakBeforeBraces: Allman 48 | BreakBeforeInheritanceComma: false 49 | BreakInheritanceList: BeforeColon 50 | BreakBeforeTernaryOperators: true 51 | BreakConstructorInitializers: BeforeColon 52 | BreakInheritanceList: BeforeColon 53 | BreakAfterJavaFieldAnnotations: false 54 | BreakStringLiterals: true 55 | ColumnLimit: 160 56 | CommentPragmas: '^ IWYU pragma:' 57 | CompactNamespaces: false 58 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 59 | ConstructorInitializerIndentWidth: 4 60 | ContinuationIndentWidth: 4 61 | Cpp11BracedListStyle: true 62 | DeriveLineEnding: true 63 | DerivePointerAlignment: false 64 | DisableFormat: false 65 | ExperimentalAutoDetectBinPacking: false 66 | FixNamespaceComments: true 67 | ForEachMacros: 68 | - foreach 69 | - Q_FOREACH 70 | - BOOST_FOREACH 71 | IncludeBlocks: Preserve 72 | IncludeCategories: 73 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 74 | Priority: 2 75 | SortPriority: 0 76 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 77 | Priority: 3 78 | SortPriority: 0 79 | - Regex: '.*' 80 | Priority: 1 81 | SortPriority: 0 82 | IncludeIsMainRegex: '(Test)?$' 83 | IncludeIsMainSourceRegex: '' 84 | IndentCaseLabels: true 85 | # IndentCaseBlocks: false 86 | IndentGotoLabels: true 87 | IndentPPDirectives: AfterHash 88 | IndentExternBlock: AfterExternBlock 89 | IndentWidth: 4 90 | IndentWrappedFunctionNames: false 91 | JavaScriptQuotes: Leave 92 | JavaScriptWrapImports: true 93 | KeepEmptyLinesAtTheStartOfBlocks: true 94 | MacroBlockBegin: '' 95 | MacroBlockEnd: '' 96 | MaxEmptyLinesToKeep: 1 97 | NamespaceIndentation: None 98 | ObjCBinPackProtocolList: Auto 99 | ObjCBlockIndentWidth: 2 100 | ObjCSpaceAfterProperty: false 101 | ObjCSpaceBeforeProtocolList: true 102 | PackConstructorInitializers: Never 103 | PenaltyBreakAssignment: 2 104 | PenaltyBreakBeforeFirstCallParameter: 19 105 | PenaltyBreakComment: 300 106 | PenaltyBreakFirstLessLess: 120 107 | PenaltyBreakString: 1000 108 | PenaltyBreakTemplateDeclaration: 10 109 | PenaltyExcessCharacter: 1000000 110 | PenaltyReturnTypeOnItsOwnLine: 60 111 | PointerAlignment: Right 112 | ReflowComments: true 113 | SortIncludes: true 114 | SortUsingDeclarations: true 115 | SpaceAfterCStyleCast: false 116 | SpaceAfterLogicalNot: false 117 | SpaceAfterTemplateKeyword: true 118 | SpaceBeforeAssignmentOperators: true 119 | SpaceBeforeCpp11BracedList: false 120 | SpaceBeforeCtorInitializerColon: true 121 | SpaceBeforeInheritanceColon: true 122 | SpaceBeforeParens: ControlStatements 123 | SpaceBeforeRangeBasedForLoopColon: true 124 | SpaceInEmptyBlock: false 125 | SpaceInEmptyParentheses: false 126 | SpacesBeforeTrailingComments: 2 127 | SpacesInAngles: false 128 | SpacesInConditionalStatement: false 129 | SpacesInContainerLiterals: true 130 | SpacesInCStyleCastParentheses: false 131 | SpacesInParentheses: false 132 | SpacesInSquareBrackets: false 133 | SpaceBeforeSquareBrackets: false 134 | Standard: Cpp11 135 | StatementMacros: 136 | - Q_UNUSED 137 | - QT_REQUIRE_VERSION 138 | TabWidth: 4 139 | UseCRLF: false 140 | UseTab: Never 141 | ... 142 | 143 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.qbs.user 20 | *.qbs.user.* 21 | *.moc 22 | moc_*.cpp 23 | moc_*.h 24 | qrc_*.cpp 25 | ui_*.h 26 | Makefile* 27 | *build-* 28 | 29 | # QtCreator 30 | 31 | *.autosave 32 | 33 | # QtCtreator Qml 34 | *.qmlproject.user 35 | *.qmlproject.user.* 36 | 37 | # QtCtreator CMake 38 | CMakeLists.txt.user* 39 | 40 | # Dirs 41 | bin/ 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sébastien CAUX 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qvterminal 2 | A cross platform QT widget to emulate VT100 terminal 3 | -------------------------------------------------------------------------------- /src/qvtchar.cpp: -------------------------------------------------------------------------------- 1 | #include "qvtchar.h" 2 | 3 | QVTChar::QVTChar(QChar c, const QVTCharFormat &format) 4 | { 5 | _c = c; 6 | _format = format; 7 | } 8 | 9 | QChar QVTChar::c() const 10 | { 11 | return _c; 12 | } 13 | 14 | void QVTChar::setC(QChar c) 15 | { 16 | _c = c; 17 | } 18 | 19 | QVTCharFormat &QVTChar::format() 20 | { 21 | return _format; 22 | } 23 | 24 | const QVTCharFormat &QVTChar::format() const 25 | { 26 | return _format; 27 | } 28 | -------------------------------------------------------------------------------- /src/qvtchar.h: -------------------------------------------------------------------------------- 1 | #ifndef QVTCHAR_H 2 | #define QVTCHAR_H 3 | 4 | #include "qvtcharformat.h" 5 | 6 | class QVTChar 7 | { 8 | public: 9 | QVTChar(QChar c, const QVTCharFormat &format); 10 | 11 | QChar c() const; 12 | void setC(QChar c); 13 | 14 | QVTCharFormat &format(); 15 | const QVTCharFormat &format() const; 16 | 17 | protected: 18 | QChar _c; 19 | QVTCharFormat _format; 20 | }; 21 | 22 | #endif // QVTCHAR_H 23 | -------------------------------------------------------------------------------- /src/qvtcharformat.cpp: -------------------------------------------------------------------------------- 1 | #include "qvtcharformat.h" 2 | 3 | QVTCharFormat::QVTCharFormat() 4 | { 5 | } 6 | 7 | const QFont &QVTCharFormat::font() const 8 | { 9 | return _font; 10 | } 11 | 12 | QFont &QVTCharFormat::font() 13 | { 14 | return _font; 15 | } 16 | 17 | void QVTCharFormat::setFont(const QFont &font) 18 | { 19 | _font = font; 20 | } 21 | 22 | const QColor &QVTCharFormat::foreground() const 23 | { 24 | return _foreground; 25 | } 26 | 27 | void QVTCharFormat::setForeground(const QColor &foreground) 28 | { 29 | _foreground = foreground; 30 | } 31 | 32 | const QColor &QVTCharFormat::background() const 33 | { 34 | return _background; 35 | } 36 | 37 | void QVTCharFormat::setBackground(const QColor &background) 38 | { 39 | _background = background; 40 | } 41 | -------------------------------------------------------------------------------- /src/qvtcharformat.h: -------------------------------------------------------------------------------- 1 | #ifndef QVTCHARFORMAT_H 2 | #define QVTCHARFORMAT_H 3 | 4 | #include 5 | #include 6 | 7 | class QVTCharFormat 8 | { 9 | public: 10 | QVTCharFormat(); 11 | 12 | const QFont &font() const; 13 | QFont &font(); 14 | void setFont(const QFont &font); 15 | 16 | const QColor &foreground() const; 17 | void setForeground(const QColor &foreground); 18 | 19 | const QColor &background() const; 20 | void setBackground(const QColor &background); 21 | 22 | protected: 23 | QFont _font; 24 | QColor _foreground; 25 | QColor _background; 26 | }; 27 | 28 | #endif // QVTCHARFORMAT_H 29 | -------------------------------------------------------------------------------- /src/qvterminal.cpp: -------------------------------------------------------------------------------- 1 | #include "qvterminal.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 13 | # include 14 | #else 15 | # include 16 | #endif 17 | 18 | #include 19 | 20 | static const int xMargin = 3; 21 | static const int yMargin = 3; 22 | 23 | QVTerminal::QVTerminal(QWidget *parent) 24 | : QAbstractScrollArea(parent) 25 | { 26 | _device = Q_NULLPTR; 27 | 28 | setCursorPos(QPoint(0, 0)); 29 | _cursorTimer.start(QGuiApplication::styleHints()->cursorFlashTime() / 2); 30 | _cvisible = true; 31 | connect(&_cursorTimer, &QTimer::timeout, this, &QVTerminal::toggleCursor); 32 | 33 | _echo = false; 34 | _crlf = false; 35 | _state = QVTerminal::Text; 36 | 37 | QVTCharFormat format; 38 | QFont font; 39 | font.setFamily("monospace"); 40 | font.setStyleHint(QFont::Monospace); 41 | font.setPointSize(13); 42 | format.setFont(font); 43 | format.setForeground(QColor(187, 187, 187)); 44 | format.setBackground(QColor(0x23, 0x26, 0x29)); 45 | setFormat(format); 46 | 47 | _layout = new QVTLayout(); 48 | 49 | _pasteAction = new QAction("Paste"); 50 | _pasteAction->setShortcut(QKeySequence("Ctrl+V")); 51 | connect(_pasteAction, &QAction::triggered, this, &QVTerminal::paste); 52 | addAction(_pasteAction); 53 | 54 | _clearAction = new QAction("Clear all"); 55 | connect(_clearAction, &QAction::triggered, this, &QVTerminal::clear); 56 | addAction(_clearAction); 57 | 58 | _vt = new VT100(this); 59 | _useFormaValue_Y = false; 60 | 61 | viewport()->setCursor(QCursor(Qt::IBeamCursor)); 62 | } 63 | 64 | QVTerminal::~QVTerminal() 65 | { 66 | } 67 | 68 | void QVTerminal::setIODevice(QIODevice *device) 69 | { 70 | _device = device; 71 | if (_device) 72 | { 73 | connect(_device, &QIODevice::readyRead, this, &QVTerminal::read); 74 | read(); 75 | } 76 | } 77 | 78 | void QVTerminal::appendData(const QByteArray &data) 79 | { 80 | QString text; 81 | 82 | setUpdatesEnabled(false); 83 | 84 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 85 | QTextCodec *textCodec = QTextCodec::codecForName("UTF-8"); 86 | QString dataString = textCodec->toUnicode(data); 87 | #else 88 | QString dataString = QStringDecoder(QStringDecoder::Utf8)(data); 89 | #endif 90 | 91 | QString::const_iterator it = dataString.cbegin(); 92 | while (it != dataString.cend()) 93 | { 94 | QChar c = *it; 95 | switch (_state) 96 | { 97 | case QVTerminal::Text: 98 | if (c.toLatin1() == 0x1B) 99 | { 100 | appendString(text); 101 | text.clear(); 102 | _state = QVTerminal::Escape; 103 | } 104 | else if (c == '\n') 105 | { 106 | appendString(text); 107 | text.clear(); 108 | _layout->appendLine(); 109 | 110 | setCursorPos(0, _cursorPos.y() + 1); 111 | } 112 | else if (c == '\b') 113 | { 114 | if (_cursorPos.x()) 115 | { 116 | appendString(text); 117 | text.clear(); 118 | setCursorPos(_cursorPos.x() - 1, _cursorPos.y()); 119 | } 120 | } 121 | else if (c.isPrint()) 122 | { 123 | text.append(c); 124 | } 125 | break; 126 | 127 | case QVTerminal::Escape: 128 | _formatValue = 0; 129 | _formatValue_Y = 0; 130 | _useFormaValue_Y = false; 131 | if (c == '[') 132 | { 133 | _state = QVTerminal::Format; 134 | } 135 | else if (c == '(') 136 | { 137 | _state = QVTerminal::ResetFont; 138 | } 139 | break; 140 | 141 | case QVTerminal::Format: 142 | if (c >= '0' && c <= '9') 143 | { 144 | if (_useFormaValue_Y) 145 | { 146 | _formatValue_Y = _formatValue_Y * 10 + (c.cell() - '0'); 147 | } 148 | else 149 | { 150 | _formatValue = _formatValue * 10 + (c.cell() - '0'); 151 | } 152 | } 153 | else 154 | { 155 | _useFormaValue_Y = false; 156 | if (c == ';' || c == 'm') // Format 157 | { 158 | if (_formatValue == 0) // reset format 159 | { 160 | _curentFormat = _format; 161 | } 162 | else if (_formatValue == 4) // underline 163 | { 164 | _curentFormat.font().setUnderline(true); 165 | } 166 | else if (_formatValue == 7) // reverse 167 | { 168 | QColor foreground = _curentFormat.foreground(); 169 | _curentFormat.setForeground(_curentFormat.background()); 170 | _curentFormat.setBackground(foreground); 171 | } 172 | else if (_formatValue / 10 == 3) // foreground 173 | { 174 | _curentFormat.setForeground(vt100color(static_cast(_formatValue % 10) + '0')); 175 | } 176 | else if (_formatValue / 10 == 4) // background 177 | { 178 | _curentFormat.setBackground(vt100color(static_cast(_formatValue % 10) + '0')); 179 | } 180 | 181 | if (c == ';') 182 | { 183 | _formatValue_Y = 0; 184 | _useFormaValue_Y = true; 185 | 186 | _state = QVTerminal::Format; 187 | } 188 | else 189 | { 190 | _state = QVTerminal::Text; 191 | } 192 | } 193 | else if (c >= 'A' && c <= 'D') // Cursor command 194 | { 195 | // move at least one char 196 | if (!_formatValue) 197 | { 198 | _formatValue++; 199 | } 200 | 201 | switch (c.toLatin1()) 202 | { 203 | case 'A': // up 204 | setCursorPos(_cursorPos.x(), qMax(_cursorPos.y() - _formatValue, 0)); 205 | break; 206 | 207 | case 'B': // down 208 | setCursorPos(_cursorPos.x(), _cursorPos.y() + _formatValue); 209 | break; 210 | 211 | case 'C': // right 212 | setCursorPos(_cursorPos.x() + _formatValue, _cursorPos.y()); 213 | break; 214 | 215 | case 'D': // left 216 | setCursorPos(qMax(_cursorPos.x() - _formatValue, 0), _cursorPos.y()); 217 | break; 218 | 219 | default: 220 | break; 221 | } 222 | _state = QVTerminal::Text; 223 | } 224 | else if (c == 'H') 225 | { 226 | setCursorPos(_formatValue, _formatValue_Y); 227 | _state = QVTerminal::Text; 228 | } 229 | else if (c == 'J') 230 | { 231 | switch (_formatValue) 232 | { 233 | case 0: 234 | clear(); 235 | break; 236 | 237 | case 1: 238 | case 2: 239 | default: 240 | qDebug() << __FUNCTION__ << "unimplement J function!"; 241 | break; 242 | } 243 | _state = QVTerminal::Text; 244 | } 245 | else if (c == 'K') 246 | { 247 | switch (_formatValue) 248 | { 249 | case 0: 250 | removeStringFromCursor(RIGHT_DIRECT); 251 | break; 252 | 253 | case 1: 254 | case 2: 255 | default: 256 | qDebug() << __FUNCTION__ << "unimplement K function!"; 257 | break; 258 | } 259 | _state = QVTerminal::Text; 260 | } 261 | else if (c == 'P') 262 | { 263 | removeStringFromCursor(LEFT_DIRECT, _formatValue); 264 | removeStringFromCursor(RIGHT_DIRECT); 265 | _state = QVTerminal::Text; 266 | } 267 | else 268 | { 269 | _state = QVTerminal::Text; 270 | } 271 | } 272 | break; 273 | 274 | case QVTerminal::ResetFont: 275 | _curentFormat = _format; 276 | _state = QVTerminal::Text; 277 | break; 278 | } 279 | it++; 280 | } 281 | appendString(text); 282 | 283 | bool scroll = (verticalScrollBar()->value() >= verticalScrollBar()->maximum() - 4); 284 | verticalScrollBar()->setRange(0, _layout->lineCount() * _ch + yMargin * 2 - viewport()->size().height()); 285 | if (scroll) 286 | { 287 | verticalScrollBar()->setValue(verticalScrollBar()->maximum()); 288 | } 289 | 290 | setUpdatesEnabled(true); 291 | update(); 292 | } 293 | 294 | void QVTerminal::paste() 295 | { 296 | QByteArray data; 297 | data.append(QApplication::clipboard()->text().toUtf8()); 298 | writeData(data); 299 | } 300 | 301 | void QVTerminal::clear() 302 | { 303 | setUpdatesEnabled(false); 304 | _layout->clear(); 305 | setCursorPos(0, 0); 306 | setUpdatesEnabled(true); 307 | } 308 | 309 | QColor QVTerminal::vt100color(char c) 310 | { 311 | switch (c) 312 | { 313 | case '1': 314 | return QColor(Qt::red); 315 | 316 | case '2': 317 | return QColor(Qt::green); 318 | 319 | case '3': 320 | return QColor(Qt::yellow); 321 | 322 | case '4': 323 | return QColor(Qt::blue); 324 | 325 | case '5': 326 | return QColor(Qt::magenta); 327 | 328 | case '6': 329 | return QColor(Qt::cyan); 330 | 331 | case '7': 332 | return QColor(Qt::white); 333 | 334 | default: 335 | return QColor(Qt::black); 336 | } 337 | } 338 | 339 | void QVTerminal::read() 340 | { 341 | if (!_device) 342 | { 343 | return; 344 | } 345 | if (_device->isReadable()) 346 | { 347 | appendData(_device->readAll()); 348 | } 349 | } 350 | 351 | void QVTerminal::appendString(const QString &str) 352 | { 353 | foreach (QChar c, str) 354 | { 355 | QVTChar termChar(c, _curentFormat); 356 | _layout->lineAt(_cursorPos.y()).replace(termChar, _cursorPos.x()); 357 | setCursorPos(_cursorPos.x() + 1, _cursorPos.y()); 358 | } 359 | } 360 | 361 | void QVTerminal::removeStringFromCursor(Direction direction, int len) 362 | { 363 | // size limitation 364 | int remove_size = 0; 365 | 366 | if (len < 0) 367 | { 368 | len = INT_MAX; 369 | } 370 | 371 | if (direction > 0) 372 | { 373 | // right direction 374 | remove_size = qMin(static_cast(len), _layout->lineAt(_cursorPos.y()).size() - _cursorPos.x()); 375 | } 376 | else 377 | { 378 | // left direction 379 | remove_size = qMin(len, _cursorPos.x()); 380 | } 381 | 382 | // remove operation 383 | QVTChar termChar('\x7F', _curentFormat); 384 | int offset = 0; 385 | for (int i = 0; i < remove_size; ++i) 386 | { 387 | if (direction < 0) 388 | { 389 | offset = -i; 390 | } 391 | else 392 | { 393 | offset = i; 394 | } 395 | _layout->lineAt(_cursorPos.y()).replace(termChar, _cursorPos.x() + offset); 396 | } 397 | } 398 | 399 | void QVTerminal::toggleCursor() 400 | { 401 | _cvisible = !_cvisible; 402 | viewport()->update(); 403 | } 404 | 405 | void QVTerminal::setCursorPos(int x, int y) 406 | { 407 | setCursorPos(QPoint(x, y)); 408 | } 409 | 410 | void QVTerminal::setCursorPos(QPoint cursorPos) 411 | { 412 | if (cursorPos != _cursorPos) 413 | { 414 | _cursorPos = cursorPos; 415 | emit cursorMoved(cursorPos); 416 | } 417 | } 418 | 419 | QPoint QVTerminal::cursorPos() const 420 | { 421 | return _cursorPos; 422 | } 423 | 424 | QPoint QVTerminal::posToCursor(const QPoint &cursorPos) const 425 | { 426 | return QPoint((cursorPos.x() - xMargin) / _cw, (cursorPos.y() - yMargin + verticalScrollBar()->value()) / _ch); 427 | } 428 | 429 | bool QVTerminal::crlf() const 430 | { 431 | return _crlf; 432 | } 433 | 434 | void QVTerminal::setCrlf(bool crlf) 435 | { 436 | _crlf = crlf; 437 | } 438 | 439 | void QVTerminal::writeData(const QByteArray &data) 440 | { 441 | if (_device && _device->isWritable()) 442 | { 443 | _device->write(data); 444 | } 445 | if (_echo) 446 | { 447 | appendData(data); 448 | } 449 | } 450 | 451 | bool QVTerminal::echo() const 452 | { 453 | return _echo; 454 | } 455 | 456 | void QVTerminal::setEcho(bool echo) 457 | { 458 | _echo = echo; 459 | } 460 | 461 | const QVTCharFormat &QVTerminal::format() const 462 | { 463 | return _format; 464 | } 465 | 466 | void QVTerminal::setFormat(const QVTCharFormat &format) 467 | { 468 | _format = format; 469 | _curentFormat = format; 470 | 471 | QFontMetrics fm(_format.font()); 472 | _cw = fm.boundingRect('M').width(); 473 | _ch = fm.height(); 474 | } 475 | 476 | void QVTerminal::keyPressEvent(QKeyEvent *event) 477 | { 478 | QByteArray data; 479 | QString text = event->text(); 480 | 481 | if (text == "\r") 482 | { 483 | if (_crlf) 484 | { 485 | data.append("\r"); 486 | } 487 | data.append("\n"); 488 | } 489 | else 490 | { 491 | data.append(_vt->dataFromKey(event->text(), event->key(), event->modifiers())); 492 | } 493 | 494 | writeData(data); 495 | 496 | // QAbstractScrollArea::keyPressEvent(event); 497 | } 498 | 499 | void QVTerminal::paintEvent(QPaintEvent *paintEvent) 500 | { 501 | Q_UNUSED(paintEvent); 502 | 503 | QPainter p(viewport()); 504 | 505 | p.setPen(QPen()); 506 | p.fillRect(viewport()->rect(), QColor(0x23, 0x26, 0x29)); 507 | 508 | p.translate(QPoint(xMargin, -verticalScrollBar()->value() + yMargin)); 509 | p.setBrush(_format.background()); 510 | p.setFont(_format.font()); 511 | 512 | int firstLine = verticalScrollBar()->value() / _ch; 513 | int lastLine = viewport()->size().height() / _ch + firstLine + 1; 514 | if (lastLine > _layout->lineCount()) 515 | { 516 | lastLine = _layout->lineCount(); 517 | } 518 | 519 | QPoint pos(0, firstLine * _ch); 520 | for (int l = firstLine; l < lastLine; l++) // render only visible lines 521 | { 522 | const QVTLine &line = _layout->lineAt(l); 523 | 524 | pos.setY(pos.y() + _ch); 525 | pos.setX(0); 526 | for (int c = 0; c < line.size(); c++) 527 | { 528 | const QVTChar &vtChar = line.chars()[c]; 529 | QColor foreground = vtChar.format().foreground(); 530 | QColor background = vtChar.format().background(); 531 | bool inverted = false; 532 | 533 | // select range 534 | if (!_endSelectPos.isNull()) 535 | { 536 | if (l > _startSelectPos.y() && l < _endSelectPos.y()) 537 | { 538 | inverted = true; 539 | } 540 | if (l == _startSelectPos.y()) 541 | { 542 | if (l == _endSelectPos.y()) 543 | { 544 | inverted = (c >= _startSelectPos.x() && c <= _endSelectPos.x()); 545 | } 546 | else 547 | { 548 | inverted = (c >= _startSelectPos.x()); 549 | } 550 | } 551 | else if (l == _endSelectPos.y()) 552 | { 553 | inverted = (c <= _endSelectPos.x()); 554 | } 555 | } 556 | 557 | if (inverted) 558 | { 559 | qSwap(foreground, background); 560 | } 561 | 562 | // draw background 563 | if (background != _format.background()) 564 | { 565 | p.fillRect(QRect(pos, QSize(_cw, -_ch)), background); 566 | } 567 | 568 | // draw foreground 569 | p.setPen(foreground); 570 | p.drawText(QRect(pos, QSize(_cw, -_ch)).normalized(), Qt::AlignCenter, QString(vtChar.c())); 571 | 572 | pos.setX(pos.x() + _cw); 573 | } 574 | } 575 | 576 | if (_cvisible) 577 | { 578 | p.fillRect(QRect(_cursorPos.x() * _cw, _cursorPos.y() * _ch, _cw, _ch), QColor(187, 187, 187, 187)); 579 | } 580 | } 581 | 582 | void QVTerminal::resizeEvent(QResizeEvent *event) 583 | { 584 | Q_UNUSED(event) 585 | verticalScrollBar()->setPageStep(_ch * 10); 586 | verticalScrollBar()->setSingleStep(_ch); 587 | verticalScrollBar()->setRange(0, _layout->lineCount() * _ch + yMargin * 2 - viewport()->size().height()); 588 | } 589 | 590 | void QVTerminal::mousePressEvent(QMouseEvent *event) 591 | { 592 | if (event->button() == Qt::LeftButton) 593 | { 594 | _endSelectPos = QPoint(); 595 | _startCursorSelectPos = posToCursor(event->pos()); 596 | setMouseTracking(true); 597 | } 598 | if (event->button() == Qt::MiddleButton) 599 | { 600 | if (QApplication::clipboard()->supportsSelection()) 601 | { 602 | QByteArray data; 603 | data.append(QApplication::clipboard()->text(QClipboard::Selection).toUtf8()); 604 | writeData(data); 605 | } 606 | } 607 | QAbstractScrollArea::mousePressEvent(event); 608 | } 609 | 610 | void QVTerminal::mouseMoveEvent(QMouseEvent *event) 611 | { 612 | if (!_startCursorSelectPos.isNull()) 613 | { 614 | _endSelectPos = posToCursor(event->pos()); 615 | if ((_startCursorSelectPos.y() > _endSelectPos.y()) 616 | || (_startCursorSelectPos.y() == _endSelectPos.y() && _startCursorSelectPos.x() > _endSelectPos.x())) 617 | { 618 | _startSelectPos = posToCursor(event->pos()); 619 | _endSelectPos = _startCursorSelectPos; 620 | } 621 | else 622 | { 623 | _startSelectPos = _startCursorSelectPos; 624 | _endSelectPos = posToCursor(event->pos()); 625 | } 626 | viewport()->update(); 627 | } 628 | } 629 | 630 | void QVTerminal::mouseReleaseEvent(QMouseEvent *event) 631 | { 632 | if (event->button() == Qt::LeftButton) 633 | { 634 | if (_startCursorSelectPos == _endSelectPos) 635 | { 636 | _startSelectPos = QPoint(); 637 | _endSelectPos = QPoint(); 638 | } 639 | _startCursorSelectPos = QPoint(); 640 | viewport()->update(); 641 | setMouseTracking(false); 642 | } 643 | QAbstractScrollArea::mouseReleaseEvent(event); 644 | } 645 | 646 | #ifndef QT_NO_CONTEXTMENU 647 | void QVTerminal::contextMenuEvent(QContextMenuEvent *event) 648 | { 649 | QMenu menu(this); 650 | _pasteAction->setEnabled(!QApplication::clipboard()->text().isEmpty()); 651 | menu.addAction(_pasteAction); 652 | menu.addAction(_clearAction); 653 | menu.exec(event->globalPos()); 654 | } 655 | #endif // QT_NO_CONTEXTMENU 656 | 657 | bool QVTerminal::viewportEvent(QEvent *event) 658 | { 659 | return QAbstractScrollArea::viewportEvent(event); 660 | } 661 | -------------------------------------------------------------------------------- /src/qvterminal.h: -------------------------------------------------------------------------------- 1 | #ifndef QVTERMINAL_H 2 | #define QVTERMINAL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "qvtlayout.h" 12 | 13 | #include 14 | 15 | class QVTerminal : public QAbstractScrollArea 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | explicit QVTerminal(QWidget *parent = nullptr); 21 | ~QVTerminal() override; 22 | 23 | enum Direction 24 | { 25 | RIGHT_DIRECT = 1, 26 | LEFT_DIRECT = -1, 27 | }; 28 | 29 | void setIODevice(QIODevice *device); 30 | 31 | // style 32 | const QVTCharFormat &format() const; 33 | void setFormat(const QVTCharFormat &format); 34 | 35 | // mode 36 | bool echo() const; 37 | void setEcho(bool echo); 38 | 39 | bool crlf() const; 40 | void setCrlf(bool crlf); 41 | 42 | QPoint cursorPos() const; 43 | QPoint posToCursor(const QPoint &cursorPos) const; 44 | 45 | signals: 46 | void cursorMoved(QPoint cursorPos); 47 | 48 | public slots: 49 | void writeData(const QByteArray &data); 50 | 51 | void paste(); 52 | void clear(); 53 | 54 | protected slots: 55 | void read(); 56 | void appendData(const QByteArray &data); 57 | void appendString(const QString &str); 58 | void removeStringFromCursor(Direction direction = RIGHT_DIRECT, int len = INT_MAX); 59 | 60 | void toggleCursor(); 61 | 62 | private: 63 | QIODevice *_device; 64 | 65 | // parser 66 | enum State 67 | { 68 | Text, 69 | Escape, 70 | Format, 71 | ResetFont 72 | }; 73 | State _state; 74 | int _formatValue; 75 | int _formatValue_Y; 76 | bool _useFormaValue_Y; 77 | 78 | // cursor 79 | void setCursorPos(int x, int y); 80 | void setCursorPos(QPoint cursorPos); 81 | QVTCharFormat _format; 82 | QVTCharFormat _curentFormat; 83 | int _cw; 84 | int _ch; 85 | QPoint _cursorPos; 86 | QTimer _cursorTimer; 87 | bool _cvisible; 88 | 89 | // select 90 | QPoint _startCursorSelectPos; 91 | QPoint _startSelectPos; 92 | QPoint _endSelectPos; 93 | 94 | // data 95 | QVTLayout *_layout; 96 | 97 | // mode 98 | bool _echo; 99 | bool _crlf; 100 | 101 | QAction *_pasteAction; 102 | QAction *_clearAction; 103 | VT *_vt; 104 | 105 | // QWidget interface 106 | protected: 107 | void keyPressEvent(QKeyEvent *event) override; 108 | void paintEvent(QPaintEvent *event) override; 109 | void resizeEvent(QResizeEvent *event) override; 110 | void mousePressEvent(QMouseEvent *event) override; 111 | void mouseMoveEvent(QMouseEvent *event) override; 112 | void mouseReleaseEvent(QMouseEvent *event) override; 113 | #ifndef QT_NO_CONTEXTMENU 114 | void contextMenuEvent(QContextMenuEvent *event) override; 115 | #endif // QT_NO_CONTEXTMENU 116 | 117 | // QAbstractScrollArea interface 118 | protected: 119 | bool viewportEvent(QEvent *event) override; 120 | QColor vt100color(char c); 121 | }; 122 | 123 | #endif // QVTERMINAL_H 124 | -------------------------------------------------------------------------------- /src/qvterminal.pri: -------------------------------------------------------------------------------- 1 | 2 | INCLUDEPATH += $$PWD 3 | 4 | SOURCES += \ 5 | $$PWD/qvterminal.cpp \ 6 | $$PWD/qvtline.cpp \ 7 | $$PWD/qvtchar.cpp \ 8 | $$PWD/qvtcharformat.cpp \ 9 | $$PWD/qvtlayout.cpp \ 10 | $$PWD/vt/vt.cpp \ 11 | $$PWD/vt/vt100.cpp 12 | 13 | HEADERS += \ 14 | $$PWD/qvterminal.h \ 15 | $$PWD/qvtline.h \ 16 | $$PWD/qvtchar.h \ 17 | $$PWD/qvtcharformat.h \ 18 | $$PWD/qvtlayout.h \ 19 | $$PWD/vt/vt.h \ 20 | $$PWD/vt/vt100.h 21 | -------------------------------------------------------------------------------- /src/qvtlayout.cpp: -------------------------------------------------------------------------------- 1 | #include "qvtlayout.h" 2 | 3 | QVTLayout::QVTLayout() 4 | { 5 | appendLine(); 6 | } 7 | 8 | int QVTLayout::lineCount() const 9 | { 10 | return _lines.count(); 11 | } 12 | 13 | QVTLine &QVTLayout::lineAt(int i) 14 | { 15 | return _lines[i]; 16 | } 17 | 18 | void QVTLayout::appendLine() 19 | { 20 | _lines.append(QVTLine()); 21 | } 22 | 23 | void QVTLayout::clear() 24 | { 25 | _lines.clear(); 26 | appendLine(); 27 | } 28 | -------------------------------------------------------------------------------- /src/qvtlayout.h: -------------------------------------------------------------------------------- 1 | #ifndef QVTLAYOUT_H 2 | #define QVTLAYOUT_H 3 | 4 | #include "qvtline.h" 5 | 6 | class QVTLayout 7 | { 8 | public: 9 | QVTLayout(); 10 | 11 | int lineCount() const; 12 | QVTLine &lineAt(int i); 13 | 14 | void appendLine(); 15 | 16 | void clear(); 17 | 18 | protected: 19 | QList _lines; 20 | }; 21 | 22 | #endif // QVTLAYOUT_H 23 | -------------------------------------------------------------------------------- /src/qvtline.cpp: -------------------------------------------------------------------------------- 1 | #include "qvtline.h" 2 | 3 | QVTLine::QVTLine() 4 | { 5 | } 6 | 7 | void QVTLine::append(const QVTChar &c) 8 | { 9 | _chars.append(c); 10 | } 11 | 12 | void QVTLine::insert(const QVTChar &c, int pos) 13 | { 14 | if (pos > _chars.size()) 15 | { 16 | return; 17 | } 18 | _chars.insert(pos, c); 19 | } 20 | 21 | void QVTLine::replace(const QVTChar &c, int pos) 22 | { 23 | if (pos > _chars.size()) 24 | { 25 | return; 26 | } 27 | if (pos < _chars.size()) 28 | { 29 | _chars[pos] = c; 30 | return; 31 | } 32 | _chars.insert(pos, c); 33 | } 34 | 35 | const QList &QVTLine::chars() const 36 | { 37 | return _chars; 38 | } 39 | 40 | QString QVTLine::text() const 41 | { 42 | QString text; 43 | for (const QVTChar &c : _chars) 44 | { 45 | text.append(c.c()); 46 | } 47 | return text; 48 | } 49 | 50 | QString QVTLine::text(qsizetype position, qsizetype n) const 51 | { 52 | if (position >= _chars.size() || position < 0) 53 | { 54 | return QString(); 55 | } 56 | 57 | qsizetype size = n; 58 | if (position + size > _chars.size()) 59 | { 60 | size = _chars.size() - position; 61 | } 62 | 63 | QString text; 64 | for (qsizetype col = position; col < position + size; col++) 65 | { 66 | text.append(_chars[col].c()); 67 | } 68 | return text; 69 | } 70 | 71 | qsizetype QVTLine::size() const 72 | { 73 | return _chars.size(); 74 | } 75 | -------------------------------------------------------------------------------- /src/qvtline.h: -------------------------------------------------------------------------------- 1 | #ifndef QVTLINE_H 2 | #define QVTLINE_H 3 | 4 | #include "qvtchar.h" 5 | 6 | class QVTLine 7 | { 8 | public: 9 | QVTLine(); 10 | 11 | void append(const QVTChar &c); 12 | void insert(const QVTChar &c, int pos); 13 | void replace(const QVTChar &c, int pos); 14 | 15 | const QList &chars() const; 16 | 17 | QString text() const; 18 | QString text(qsizetype position, qsizetype n = -1) const; 19 | 20 | qsizetype size() const; 21 | 22 | protected: 23 | QList _chars; 24 | }; 25 | 26 | #endif // QVTLINE_H 27 | -------------------------------------------------------------------------------- /src/vt/vt.cpp: -------------------------------------------------------------------------------- 1 | #include "vt.h" 2 | 3 | VT::VT(QVTerminal *terminal) 4 | : _terminal(terminal) 5 | { 6 | } 7 | 8 | QVTerminal *VT::terminal() const 9 | { 10 | return _terminal; 11 | } 12 | 13 | QByteArray VT::dataFromKey(const QString &text, int key, Qt::KeyboardModifiers modifiers) 14 | { 15 | Q_UNUSED(modifiers) 16 | Q_UNUSED(key) 17 | 18 | QByteArray data; 19 | data.append(text.toUtf8()); 20 | return data; 21 | } 22 | -------------------------------------------------------------------------------- /src/vt/vt.h: -------------------------------------------------------------------------------- 1 | #ifndef VT_H 2 | #define VT_H 3 | 4 | #include 5 | #include 6 | 7 | class QVTerminal; 8 | 9 | class VT 10 | { 11 | public: 12 | VT(QVTerminal *terminal); 13 | 14 | QVTerminal *terminal() const; 15 | 16 | virtual QByteArray dataFromKey(const QString &text, int key, Qt::KeyboardModifiers modifiers); 17 | 18 | protected: 19 | QVTerminal *_terminal; 20 | }; 21 | 22 | #endif // VT_H 23 | -------------------------------------------------------------------------------- /src/vt/vt100.cpp: -------------------------------------------------------------------------------- 1 | #include "vt100.h" 2 | 3 | VT100::VT100(QVTerminal *terminal) 4 | : VT(terminal) 5 | { 6 | } 7 | 8 | QByteArray VT100::dataFromKey(const QString &text, int key, Qt::KeyboardModifiers modifiers) 9 | { 10 | Q_UNUSED(modifiers) 11 | 12 | QByteArray data; 13 | 14 | switch (key) 15 | { 16 | case Qt::Key_Up: 17 | data.append(0x1B); 18 | data.append('['); 19 | data.append('A'); 20 | break; 21 | 22 | case Qt::Key_Down: 23 | data.append(0x1B); 24 | data.append('['); 25 | data.append('B'); 26 | break; 27 | 28 | case Qt::Key_Right: 29 | data.append(0x1B); 30 | data.append('['); 31 | data.append('C'); 32 | break; 33 | 34 | case Qt::Key_Left: 35 | data.append(0x1B); 36 | data.append('['); 37 | data.append('D'); 38 | break; 39 | 40 | case Qt::Key_Home: 41 | data.append(0x01); 42 | break; 43 | 44 | case Qt::Key_End: 45 | data.append(0x05); 46 | break; 47 | 48 | case Qt::Key_Backspace: 49 | data.append(127); 50 | break; 51 | 52 | default: 53 | data.append(text.toLatin1()); 54 | break; 55 | } 56 | 57 | return data; 58 | } 59 | -------------------------------------------------------------------------------- /src/vt/vt100.h: -------------------------------------------------------------------------------- 1 | #ifndef VT100_H 2 | #define VT100_H 3 | 4 | #include "vt.h" 5 | 6 | class VT100 : public VT 7 | { 8 | public: 9 | VT100(QVTerminal *terminal); 10 | 11 | // VT interface 12 | public: 13 | QByteArray dataFromKey(const QString &text, int key, Qt::KeyboardModifiers modifiers) override; 14 | }; 15 | 16 | #endif // VT100_H 17 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | MainWindow w; 8 | w.show(); 9 | 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /test/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | MainWindow::MainWindow(QWidget *parent) : 9 | QMainWindow(parent) 10 | { 11 | terminal = new QVTerminal(); 12 | 13 | foreach (QSerialPortInfo info, QSerialPortInfo::availablePorts()) 14 | qDebug()<open(QIODevice::ReadWrite); 19 | _port->setBaudRate(125000); 20 | connect(_port, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(error(QSerialPort::SerialPortError))); 21 | terminal->setIODevice(_port);*/ 22 | 23 | // raw file test 24 | /*QFile *file = new QFile("/home/seb/Seafile/my_lib_rt/rtprog/test/holotips/out"); 25 | file->open(QIODevice::ReadOnly); 26 | terminal->setIODevice(file);*/ 27 | 28 | // process test 29 | QProcess *process = new QProcess(); 30 | QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); 31 | env.insert("TERM", "vt100"); 32 | env.insert("COLUMNS", "80"); 33 | env.insert("PS1", "> "); 34 | process->setProcessEnvironment(env); 35 | process->setProcessChannelMode(QProcess::MergedChannels); 36 | process->setArguments(QStringList()<<"--verbose"); 37 | terminal->setIODevice(process); 38 | process->start("bash"); 39 | 40 | setCentralWidget(terminal); 41 | this->resize(1024, 768); 42 | } 43 | 44 | MainWindow::~MainWindow() 45 | { 46 | delete terminal; 47 | } 48 | 49 | void MainWindow::error(QSerialPort::SerialPortError err) 50 | { 51 | qDebug()<<"error"< 7 | 8 | #include 9 | 10 | class MainWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit MainWindow(QWidget *parent = 0); 16 | ~MainWindow(); 17 | 18 | protected slots: 19 | void error(QSerialPort::SerialPortError err); 20 | 21 | private: 22 | QVTerminal *terminal; 23 | 24 | QSerialPort *_port; 25 | }; 26 | 27 | #endif // MAINWINDOW_H 28 | -------------------------------------------------------------------------------- /test/qvtest.pro: -------------------------------------------------------------------------------- 1 | 2 | QT += core gui serialport 3 | 4 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 5 | 6 | TARGET = terminal 7 | TEMPLATE = app 8 | DESTDIR = $$PWD/../bin 9 | 10 | DEFINES += QT_DEPRECATED_WARNINGS 11 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 12 | 13 | 14 | SOURCES += \ 15 | main.cpp \ 16 | mainwindow.cpp 17 | 18 | HEADERS += \ 19 | mainwindow.h 20 | 21 | include ($$PWD/../src/qvterminal.pri) 22 | --------------------------------------------------------------------------------