Integrate KodoTerm for SSH terminal sessions

This commit is contained in:
Keith Smith
2026-03-01 10:36:06 -07:00
parent c3369b8e48
commit 776ddc1a53
503 changed files with 22870 additions and 95 deletions

View File

@@ -0,0 +1,209 @@
// SPDX-License-Identifier: MIT
// Author: Diego Iastrubni <diegoiast@gmail.com>
#pragma once
#include <QColor>
#include <QElapsedTimer>
#include <QFile>
#include <QFont>
#include <QMenu>
#include <QProcessEnvironment>
#include <QScrollBar>
#include <QSocketNotifier>
#include <QTimer>
#include <QWidget>
#include <deque>
#include <functional>
#include <vector>
#include <vterm.h>
#include "KodoTermConfig.hpp"
class PtyProcess;
class KodoTerm : public QWidget {
Q_OBJECT
public:
explicit KodoTerm(QWidget *parent = nullptr);
~KodoTerm();
static void
populateThemeMenu(QMenu *parentMenu, const QString &title, TerminalTheme::ThemeFormat format,
const std::function<void(const TerminalTheme::ThemeInfo &)> &callback);
void setTheme(const TerminalTheme &theme);
void setConfig(const KodoTermConfig &config);
KodoTermConfig getConfig() const { return m_config; }
void setProgram(const QString &program) { m_program = program; }
QString program() const { return m_program; }
void setArguments(const QStringList &arguments) { m_arguments = arguments; }
QStringList arguments() const { return m_arguments; }
void setWorkingDirectory(const QString &workingDirectory) {
m_workingDirectory = workingDirectory;
}
QString workingDirectory() const { return m_workingDirectory; }
void setProcessEnvironment(const QProcessEnvironment &environment) {
m_environment = environment;
}
QProcessEnvironment processEnvironment() const { return m_environment; }
bool start(bool reset = true);
void saveState(const QString &path);
void loadState(const QString &path);
protected:
void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
bool focusNextPrevChild(bool next) override;
void focusInEvent(QFocusEvent *event) override;
void focusOutEvent(QFocusEvent *event) override;
signals:
void contextMenuRequested(QMenu *menu, const QPoint &pos);
void cwdChanged(const QString &cwd);
void finished(int exitCode, int exitStatus);
public slots:
void onPtyReadyRead(const QByteArray &data);
void onScrollValueChanged(int value);
void scrollUp(int lines = 1);
void scrollDown(int lines = 1);
void pageUp();
void pageDown();
void copyToClipboard();
void pasteFromClipboard();
void selectAll();
void clearScrollback();
void resetTerminal();
void openFileBrowser();
void kill();
void logData(const QByteArray &data);
QString logPath() const { return m_logFile.fileName(); }
void setRestoreLog(const QString &path) { m_pendingLogReplay = path; }
void scrollToBottom();
void processLogReplay();
void zoomIn();
void zoomOut();
void resetZoom();
QString foregroundProcessName() const;
bool isRoot() const;
const QString &cwd() const { return m_cwd; }
bool copyOnSelect() const { return m_config.copyOnSelect; }
void setCopyOnSelect(bool enable) { m_config.copyOnSelect = enable; }
bool pasteOnMiddleClick() const { return m_config.pasteOnMiddleClick; }
void setPasteOnMiddleClick(bool enable) { m_config.pasteOnMiddleClick = enable; }
bool mouseWheelZoom() const { return m_config.mouseWheelZoom; }
void setMouseWheelZoom(bool enable) { m_config.mouseWheelZoom = enable; }
bool visualBell() const { return m_config.visualBell; }
void setVisualBell(bool enable) { m_config.visualBell = enable; }
bool audibleBell() const { return m_config.audibleBell; }
void setAudibleBell(bool enable) { m_config.audibleBell = enable; }
private:
void setupPty();
void updateTerminalSize();
QColor mapColor(const VTermColor &c, const VTermState *state) const;
QString getTextRange(VTermPos start, VTermPos end);
bool isSelected(int row, int col) const;
VTermPos mouseToPos(const QPoint &pos) const;
// VTerm callbacks
static int onDamage(VTermRect rect, void *user);
static int onMoveRect(VTermRect dest, VTermRect src, void *user);
static int onMoveCursor(VTermPos pos, VTermPos oldpos, int visible, void *user);
static int onSetTermProp(VTermProp prop, VTermValue *val, void *user);
static int onBell(void *user);
static int onSbPushLine(int cols, const VTermScreenCell *cells, void *user);
static int onSbPopLine(int cols, VTermScreenCell *cells, void *user);
static int onOsc(int command, VTermStringFragment frag, void *user);
int pushScrollback(int cols, const VTermScreenCell *cells);
int popScrollback(int cols, VTermScreenCell *cells);
struct SavedCell {
uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
VTermScreenCellAttrs attrs;
VTermColor fg, bg;
int width;
};
using SavedLine = std::vector<SavedCell>;
PtyProcess *m_pty = nullptr;
VTerm *m_vterm = nullptr;
VTermScreen *m_vtermScreen = nullptr;
QSocketNotifier *m_notifier = nullptr;
QSize m_cellSize;
int m_cursorRow = 0;
int m_cursorCol = 0;
bool m_cursorVisible = true;
bool m_cursorBlink = false;
int m_cursorShape = 1; // VTERM_PROP_CURSORSHAPE_BLOCK
bool m_cursorBlinkState = true;
bool m_altScreen = false;
bool m_flowControlStopped = false;
bool m_restorationBannerActive = false;
QString m_restorationBannerText;
QTimer *m_restorationBannerTimer = nullptr;
int m_mouseMode = 0; // VTERM_PROP_MOUSE_NONE
QTimer *m_cursorBlinkTimer = nullptr;
QScrollBar *m_scrollBar = nullptr;
std::deque<SavedLine> m_scrollback;
bool m_selecting = false;
VTermPos m_selectionStart = {-1, -1};
VTermPos m_selectionEnd = {-1, -1};
QElapsedTimer m_clickTimer;
int m_clickCount = 0;
QPoint m_lastClickPos;
bool m_visualBellActive = false;
QString m_cwd;
QByteArray m_oscBuffer;
QString m_program;
QStringList m_arguments;
QString m_workingDirectory;
QProcessEnvironment m_environment = QProcessEnvironment::systemEnvironment();
KodoTermConfig m_config;
QFile m_logFile;
QString m_pendingLogReplay;
QFile *m_replayFile = nullptr;
bool m_restoring = false;
mutable QColor m_paletteCache[256];
mutable bool m_paletteCacheValid[256];
mutable VTermColor m_lastVTermFg, m_lastVTermBg;
mutable QColor m_lastFg, m_lastBg;
double m_avgDrawTime = 0.0;
VTermRect m_dirtyRect;
void resetDirtyRect();
bool m_dirty = false;
QImage m_backBuffer;
std::vector<VTermScreenCell> m_cellCache;
std::vector<bool> m_selectedCache;
void renderToBackbuffer();
void flushTerminal();
void damageAll();
void drawRestorationBanner(QPainter &painter);
};

View File

@@ -0,0 +1,65 @@
// SPDX-License-Identifier: MIT
// Author: Diego Iastrubni <diegoiast@gmail.com>
#pragma once
#include <QColor>
#include <QFont>
#include <QJsonObject>
#include <QList>
#include <QSettings>
#include <QString>
struct TerminalTheme {
QString name;
QColor foreground;
QColor background;
QColor palette[16];
enum class ThemeFormat { Konsole, WindowsTerminal, ITerm };
struct ThemeInfo {
QString name;
QString path;
ThemeFormat format;
};
static TerminalTheme defaultTheme();
static TerminalTheme loadTheme(const QString &path);
static TerminalTheme loadKonsoleTheme(const QString &path);
static TerminalTheme loadWindowsTerminalTheme(const QString &path);
static TerminalTheme loadITermTheme(const QString &path);
static QList<ThemeInfo> builtInThemes();
QJsonObject toJson() const;
static TerminalTheme fromJson(const QJsonObject &json);
void save(QSettings &settings, const QString &group = "Theme") const;
void load(QSettings &settings, const QString &group = "Theme");
};
class KodoTermConfig {
public:
KodoTermConfig();
KodoTermConfig(QSettings &settings);
QFont font;
bool textAntialiasing;
bool customBoxDrawing;
bool copyOnSelect;
bool pasteOnMiddleClick;
bool mouseWheelZoom;
bool visualBell;
bool audibleBell;
bool tripleClickSelectsLine;
bool enableLogging;
QString logDirectory;
QString wordSelectionRegex;
int maxScrollback;
TerminalTheme theme;
void setDefaults();
void load(const QJsonObject &json);
QJsonObject saveToJson() const;
void load(QSettings &settings);
void save(QSettings &settings) const;
};