12 Commits

Author SHA1 Message Date
Keith Smith
2b25f805cd Complete Milestone 4 interactive SSH session UX 2026-03-01 11:00:31 -07:00
Keith Smith
776ddc1a53 Integrate KodoTerm for SSH terminal sessions 2026-03-01 10:36:06 -07:00
Keith Smith
c3369b8e48 Improve terminal theming, cursor UX, and size negotiation 2026-03-01 10:14:43 -07:00
Keith Smith
20ee48db32 Add ANSI color rendering and terminal themes 2026-03-01 10:08:04 -07:00
Keith Smith
2b4f498259 Allow direct terminal typing and collapsible session panels 2026-03-01 09:58:21 -07:00
Keith Smith
614d31fa71 Restore built-in askpass helper for SSH password auth 2026-03-01 09:53:17 -07:00
Keith Smith
ceed19d517 Draft Milestone 4 scope and mark progress in docs 2026-03-01 09:50:08 -07:00
Keith Smith
2ea712db36 Start Milestone 4 interactive SSH terminal and host-key flow 2026-03-01 09:50:03 -07:00
Keith Smith
3c158269bf Fix SSH askpass helper text-file-busy race 2026-03-01 09:42:32 -07:00
Keith Smith
e2a8b874d7 Document Milestone 3 deliverables and SSH prerequisites 2026-03-01 09:37:43 -07:00
Keith Smith
71a2f2e868 Expand profile editor for SSH auth and host policy fields 2026-03-01 09:37:37 -07:00
Keith Smith
6a4bcb75eb Add threaded session backend architecture with real SSH backend 2026-03-01 09:37:34 -07:00
521 changed files with 24924 additions and 79 deletions

View File

@@ -14,6 +14,8 @@ find_package(Qt6 6.2 REQUIRED COMPONENTS Widgets Sql)
qt_standard_project_setup() qt_standard_project_setup()
add_subdirectory(third_party/KodoTerm)
add_executable(orbithub add_executable(orbithub
src/main.cpp src/main.cpp
src/profile_dialog.cpp src/profile_dialog.cpp
@@ -22,10 +24,22 @@ add_executable(orbithub
src/profile_repository.h src/profile_repository.h
src/profiles_window.cpp src/profiles_window.cpp
src/profiles_window.h src/profiles_window.h
src/session_backend.h
src/session_backend_factory.cpp
src/session_backend_factory.h
src/session_tab.cpp
src/session_tab.h
src/terminal_view.cpp
src/terminal_view.h
src/session_window.cpp src/session_window.cpp
src/session_window.h src/session_window.h
src/ssh_session_backend.cpp
src/ssh_session_backend.h
src/unsupported_session_backend.cpp
src/unsupported_session_backend.h
) )
target_link_libraries(orbithub PRIVATE Qt6::Widgets Qt6::Sql) target_link_libraries(orbithub PRIVATE Qt6::Widgets Qt6::Sql)
target_link_libraries(orbithub PRIVATE KodoTerm::KodoTerm)
install(TARGETS orbithub RUNTIME DESTINATION bin) install(TARGETS orbithub RUNTIME DESTINATION bin)

View File

@@ -8,7 +8,8 @@ Run all commands from the repository root unless noted.
sudo apt update sudo apt update
sudo apt install -y \ sudo apt install -y \
build-essential cmake ninja-build git pkg-config \ build-essential cmake ninja-build git pkg-config \
qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools \
openssh-client
cmake -S . -B build -G Ninja cmake -S . -B build -G Ninja
cmake --build build cmake --build build
@@ -20,7 +21,7 @@ cmake --build build
```bash ```bash
xcode-select --install xcode-select --install
brew update brew update
brew install cmake ninja pkg-config qt@6 brew install cmake ninja pkg-config qt@6 openssh
cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6)" cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6)"
cmake --build build cmake --build build
@@ -33,6 +34,7 @@ cmake --build build
winget install -e --id Git.Git winget install -e --id Git.Git
winget install -e --id Kitware.CMake winget install -e --id Kitware.CMake
winget install -e --id Ninja-build.Ninja winget install -e --id Ninja-build.Ninja
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
winget install -e --id Microsoft.VisualStudio.2022.BuildTools ` winget install -e --id Microsoft.VisualStudio.2022.BuildTools `
--override "--quiet --wait --norestart --add Microsoft.VisualStudio.Workload.VCTools" --override "--quiet --wait --norestart --add Microsoft.VisualStudio.Workload.VCTools"
``` ```
@@ -53,4 +55,5 @@ cmake --build build
## Notes ## Notes
- OrbitHub currently requires Qt6 Widgets and CMake 3.21+. - OrbitHub currently requires Qt6 Widgets and CMake 3.21+.
- Milestone 3 SSH sessions require an `ssh` client available on `PATH`.
- If Qt is installed in a custom location, pass `-DCMAKE_PREFIX_PATH=/path/to/Qt/6.x.x/<toolchain>` to CMake. - If Qt is installed in a custom location, pass `-DCMAKE_PREFIX_PATH=/path/to/Qt/6.x.x/<toolchain>` to CMake.

View File

@@ -64,3 +64,30 @@ OrbitHub uses a two-window model:
- Connect loads full profile details into session tab - Connect loads full profile details into session tab
- Session lifecycle states in UI (`Connecting`, `Connected`, `Failed`) with non-blocking updates - Session lifecycle states in UI (`Connecting`, `Connected`, `Failed`) with non-blocking updates
- Tag: v0-m2-done - Tag: v0-m2-done
---
## Milestone 3
- Real SSH backend using native `ssh` process (connect, disconnect, reconnect)
- Protocol backend abstraction with worker-thread execution
- RDP/VNC explicitly marked as not implemented in session UX
- Connect-time credential prompts (password/private key path) with no secret storage in DB
- Session tab controls: `Connect`, `Disconnect`, `Reconnect`, `Copy Error`
- Per-session timestamped event log and user-friendly error mapping
- Profile schema extended with `private_key_path` and `known_hosts_policy`
- Tag: v0-m3-done
---
## Milestone 4 (Draft)
- Make SSH sessions actually usable as interactive sessions inside OrbitHub
- Replace placeholder surface with interactive SSH terminal panel (stream output + send input)
- Add SSH host-key trust prompt flow for `Ask` policy
- Improve auth flow UX for password / private key at connect time
- Preserve responsive UI under active session output
- Add terminal/session utilities (`Clear Terminal`, improved output visibility)
- Keep session controls stable (`Connect`, `Disconnect`, `Reconnect`, `Copy Error`)
- Add validation and diagnostics coverage for host-key, auth, and reconnect behavior
- Planned Tag: v0-m4-done (after completion approval)

View File

@@ -43,3 +43,38 @@ Delivered:
Git: Git:
- Tag: `v0-m2-done` - Tag: `v0-m2-done`
## Milestone 3 - Real SSH Backend and Session Controls
Status: Completed
Delivered:
- Backend architecture introduced (`SessionBackend` + protocol-specific implementations)
- Worker-thread backend execution for connection lifecycle operations
- Real SSH process backend (`ssh`) with connect/disconnect/reconnect
- Unsupported protocol backend with explicit not-implemented messaging (RDP/VNC)
- Session tab controls: `Connect`, `Disconnect`, `Reconnect`, `Copy Error`
- Connect-time credential flow (password prompt / private-key path selection)
- Session event log pane with timestamps and user-friendly error mapping
- SQLite profile schema migration for `private_key_path` and `known_hosts_policy`
Git:
- Tag: `v0-m3-done`
## Milestone 4 - Interactive SSH Session UX
Status: Completed
Delivered:
- Embedded interactive SSH terminal using `KodoTerm` + vendored `libvterm`
- Native in-terminal typing for SSH sessions (no separate input box)
- ANSI/color rendering with selectable terminal themes (`Dark`, `Light`, `Solarized Dark`)
- Cross-platform SSH auth path improvements (`ssh-askpass` handling and host-key policy wiring)
- Session UX simplification: auto-connect on tab open, disconnect on tab close
- Tab-state indicators via tab color and state suffix (`Connecting`, `Connected`, `Disconnected`, `Failed`)
- Right-click tab menu for `Disconnect`, `Reconnect`, `Theme`, and `Clear`
- Collapsible events panel retained as primary diagnostics surface; inline detail/status banners removed
- Terminal behavior polish: better fixed-width font selection, cursor visibility, backspace handling, and terminal-size negotiation stability
Git:
- Tag: `v0-m4-done` (pending push)

View File

@@ -4,6 +4,8 @@
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
Q_INIT_RESOURCE(KodoTermThemes);
QApplication app(argc, argv); QApplication app(argc, argv);
ProfilesWindow window; ProfilesWindow window;

View File

@@ -2,7 +2,10 @@
#include <QComboBox> #include <QComboBox>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QFileDialog>
#include <QFormLayout> #include <QFormLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QMessageBox> #include <QMessageBox>
#include <QPushButton> #include <QPushButton>
@@ -30,9 +33,12 @@ ProfileDialog::ProfileDialog(QWidget* parent)
m_portInput(new QSpinBox(this)), m_portInput(new QSpinBox(this)),
m_usernameInput(new QLineEdit(this)), m_usernameInput(new QLineEdit(this)),
m_protocolInput(new QComboBox(this)), m_protocolInput(new QComboBox(this)),
m_authModeInput(new QComboBox(this)) m_authModeInput(new QComboBox(this)),
m_privateKeyPathInput(new QLineEdit(this)),
m_browsePrivateKeyButton(new QPushButton(QStringLiteral("Browse"), this)),
m_knownHostsPolicyInput(new QComboBox(this))
{ {
resize(420, 260); resize(520, 340);
auto* layout = new QVBoxLayout(this); auto* layout = new QVBoxLayout(this);
auto* form = new QFormLayout(); auto* form = new QFormLayout();
@@ -45,27 +51,66 @@ ProfileDialog::ProfileDialog(QWidget* parent)
m_protocolInput->addItems({QStringLiteral("SSH"), QStringLiteral("RDP"), QStringLiteral("VNC")}); m_protocolInput->addItems({QStringLiteral("SSH"), QStringLiteral("RDP"), QStringLiteral("VNC")});
m_authModeInput->addItems({QStringLiteral("Password"), QStringLiteral("Private Key")}); m_authModeInput->addItems({QStringLiteral("Password"), QStringLiteral("Private Key")});
m_knownHostsPolicyInput->addItems(
{QStringLiteral("Ask"), QStringLiteral("Strict"), QStringLiteral("Accept New"), QStringLiteral("Ignore")});
m_privateKeyPathInput->setPlaceholderText(QStringLiteral("/home/user/.ssh/id_ed25519"));
auto* privateKeyRow = new QWidget(this);
auto* privateKeyLayout = new QHBoxLayout(privateKeyRow);
privateKeyLayout->setContentsMargins(0, 0, 0, 0);
privateKeyLayout->addWidget(m_privateKeyPathInput, 1);
privateKeyLayout->addWidget(m_browsePrivateKeyButton);
connect(m_browsePrivateKeyButton,
&QPushButton::clicked,
this,
[this]() {
const QString selected = QFileDialog::getOpenFileName(this,
QStringLiteral("Select Private Key"),
QString(),
QStringLiteral("All Files (*)"));
if (!selected.isEmpty()) {
m_privateKeyPathInput->setText(selected);
}
});
connect(m_protocolInput, connect(m_protocolInput,
&QComboBox::currentTextChanged, &QComboBox::currentTextChanged,
this, this,
[this](const QString& protocol) { [this](const QString& protocol) {
m_portInput->setValue(standardPortForProtocol(protocol)); m_portInput->setValue(standardPortForProtocol(protocol));
refreshAuthFields();
}); });
connect(m_authModeInput,
&QComboBox::currentTextChanged,
this,
[this](const QString&) { refreshAuthFields(); });
form->addRow(QStringLiteral("Name"), m_nameInput); form->addRow(QStringLiteral("Name"), m_nameInput);
form->addRow(QStringLiteral("Host"), m_hostInput); form->addRow(QStringLiteral("Host"), m_hostInput);
form->addRow(QStringLiteral("Port"), m_portInput); form->addRow(QStringLiteral("Port"), m_portInput);
form->addRow(QStringLiteral("Username"), m_usernameInput); form->addRow(QStringLiteral("Username"), m_usernameInput);
form->addRow(QStringLiteral("Protocol"), m_protocolInput); form->addRow(QStringLiteral("Protocol"), m_protocolInput);
form->addRow(QStringLiteral("Auth Mode"), m_authModeInput); form->addRow(QStringLiteral("Auth Mode"), m_authModeInput);
form->addRow(QStringLiteral("Private Key"), privateKeyRow);
form->addRow(QStringLiteral("Known Hosts"), m_knownHostsPolicyInput);
auto* note = new QLabel(
QStringLiteral("Passwords are requested at connect time and are not stored."),
this);
note->setWordWrap(true);
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
layout->addLayout(form); layout->addLayout(form);
layout->addWidget(note);
layout->addWidget(buttons); layout->addWidget(buttons);
refreshAuthFields();
} }
void ProfileDialog::setDialogTitle(const QString& title) void ProfileDialog::setDialogTitle(const QString& title)
@@ -79,6 +124,7 @@ void ProfileDialog::setProfile(const Profile& profile)
m_hostInput->setText(profile.host); m_hostInput->setText(profile.host);
m_portInput->setValue(profile.port > 0 ? profile.port : 22); m_portInput->setValue(profile.port > 0 ? profile.port : 22);
m_usernameInput->setText(profile.username); m_usernameInput->setText(profile.username);
m_privateKeyPathInput->setText(profile.privateKeyPath);
const int protocolIndex = m_protocolInput->findText(profile.protocol); const int protocolIndex = m_protocolInput->findText(profile.protocol);
{ {
@@ -89,6 +135,11 @@ void ProfileDialog::setProfile(const Profile& profile)
const int authModeIndex = m_authModeInput->findText(profile.authMode); const int authModeIndex = m_authModeInput->findText(profile.authMode);
m_authModeInput->setCurrentIndex(authModeIndex >= 0 ? authModeIndex : 0); m_authModeInput->setCurrentIndex(authModeIndex >= 0 ? authModeIndex : 0);
const int knownHostsIndex = m_knownHostsPolicyInput->findText(profile.knownHostsPolicy);
m_knownHostsPolicyInput->setCurrentIndex(knownHostsIndex >= 0 ? knownHostsIndex : 0);
refreshAuthFields();
} }
Profile ProfileDialog::profile() const Profile ProfileDialog::profile() const
@@ -101,6 +152,8 @@ Profile ProfileDialog::profile() const
profile.username = m_usernameInput->text().trimmed(); profile.username = m_usernameInput->text().trimmed();
profile.protocol = m_protocolInput->currentText(); profile.protocol = m_protocolInput->currentText();
profile.authMode = m_authModeInput->currentText(); profile.authMode = m_authModeInput->currentText();
profile.privateKeyPath = m_privateKeyPathInput->text().trimmed();
profile.knownHostsPolicy = m_knownHostsPolicyInput->currentText();
return profile; return profile;
} }
@@ -120,5 +173,24 @@ void ProfileDialog::accept()
return; return;
} }
if (m_protocolInput->currentText() == QStringLiteral("SSH")
&& m_usernameInput->text().trimmed().isEmpty()) {
QMessageBox::warning(this,
QStringLiteral("Validation Error"),
QStringLiteral("Username is required for SSH profiles."));
return;
}
QDialog::accept(); QDialog::accept();
} }
void ProfileDialog::refreshAuthFields()
{
const bool isSsh = m_protocolInput->currentText() == QStringLiteral("SSH");
const bool isPrivateKey = m_authModeInput->currentText() == QStringLiteral("Private Key");
m_authModeInput->setEnabled(isSsh);
m_privateKeyPathInput->setEnabled(isSsh && isPrivateKey);
m_browsePrivateKeyButton->setEnabled(isSsh && isPrivateKey);
m_knownHostsPolicyInput->setEnabled(isSsh);
}

View File

@@ -7,6 +7,7 @@
class QComboBox; class QComboBox;
class QLineEdit; class QLineEdit;
class QPushButton;
class QSpinBox; class QSpinBox;
class ProfileDialog : public QDialog class ProfileDialog : public QDialog
@@ -30,6 +31,11 @@ private:
QLineEdit* m_usernameInput; QLineEdit* m_usernameInput;
QComboBox* m_protocolInput; QComboBox* m_protocolInput;
QComboBox* m_authModeInput; QComboBox* m_authModeInput;
QLineEdit* m_privateKeyPathInput;
QPushButton* m_browsePrivateKeyButton;
QComboBox* m_knownHostsPolicyInput;
void refreshAuthFields();
}; };
#endif #endif

View File

@@ -30,6 +30,10 @@ void bindProfileFields(QSqlQuery& query, const Profile& profile)
query.addBindValue(profile.username.trimmed()); query.addBindValue(profile.username.trimmed());
query.addBindValue(profile.protocol.trimmed()); query.addBindValue(profile.protocol.trimmed());
query.addBindValue(profile.authMode.trimmed()); query.addBindValue(profile.authMode.trimmed());
query.addBindValue(profile.privateKeyPath.trimmed());
query.addBindValue(profile.knownHostsPolicy.trimmed().isEmpty()
? QStringLiteral("Ask")
: profile.knownHostsPolicy.trimmed());
} }
Profile profileFromQuery(const QSqlQuery& query) Profile profileFromQuery(const QSqlQuery& query)
@@ -42,6 +46,11 @@ Profile profileFromQuery(const QSqlQuery& query)
profile.username = query.value(4).toString(); profile.username = query.value(4).toString();
profile.protocol = query.value(5).toString(); profile.protocol = query.value(5).toString();
profile.authMode = query.value(6).toString(); profile.authMode = query.value(6).toString();
profile.privateKeyPath = query.value(7).toString();
profile.knownHostsPolicy = query.value(8).toString();
if (profile.knownHostsPolicy.isEmpty()) {
profile.knownHostsPolicy = QStringLiteral("Ask");
}
return profile; return profile;
} }
@@ -93,12 +102,12 @@ std::vector<Profile> ProfileRepository::listProfiles(const QString& searchQuery)
QSqlQuery query(QSqlDatabase::database(m_connectionName)); QSqlQuery query(QSqlDatabase::database(m_connectionName));
if (searchQuery.trimmed().isEmpty()) { if (searchQuery.trimmed().isEmpty()) {
query.prepare(QStringLiteral( query.prepare(QStringLiteral(
"SELECT id, name, host, port, username, protocol, auth_mode " "SELECT id, name, host, port, username, protocol, auth_mode, private_key_path, known_hosts_policy "
"FROM profiles " "FROM profiles "
"ORDER BY lower(name) ASC, id ASC")); "ORDER BY lower(name) ASC, id ASC"));
} else { } else {
query.prepare(QStringLiteral( query.prepare(QStringLiteral(
"SELECT id, name, host, port, username, protocol, auth_mode " "SELECT id, name, host, port, username, protocol, auth_mode, private_key_path, known_hosts_policy "
"FROM profiles " "FROM profiles "
"WHERE lower(name) LIKE lower(?) OR lower(host) LIKE lower(?) " "WHERE lower(name) LIKE lower(?) OR lower(host) LIKE lower(?) "
"ORDER BY lower(name) ASC, id ASC")); "ORDER BY lower(name) ASC, id ASC"));
@@ -129,7 +138,7 @@ std::optional<Profile> ProfileRepository::getProfile(qint64 id) const
QSqlQuery query(QSqlDatabase::database(m_connectionName)); QSqlQuery query(QSqlDatabase::database(m_connectionName));
query.prepare(QStringLiteral( query.prepare(QStringLiteral(
"SELECT id, name, host, port, username, protocol, auth_mode " "SELECT id, name, host, port, username, protocol, auth_mode, private_key_path, known_hosts_policy "
"FROM profiles WHERE id = ?")); "FROM profiles WHERE id = ?"));
query.addBindValue(id); query.addBindValue(id);
@@ -160,8 +169,8 @@ std::optional<Profile> ProfileRepository::createProfile(const Profile& profile)
QSqlQuery query(QSqlDatabase::database(m_connectionName)); QSqlQuery query(QSqlDatabase::database(m_connectionName));
query.prepare(QStringLiteral( query.prepare(QStringLiteral(
"INSERT INTO profiles(name, host, port, username, protocol, auth_mode) " "INSERT INTO profiles(name, host, port, username, protocol, auth_mode, private_key_path, known_hosts_policy) "
"VALUES (?, ?, ?, ?, ?, ?)")); "VALUES (?, ?, ?, ?, ?, ?, ?, ?)"));
bindProfileFields(query, profile); bindProfileFields(query, profile);
if (!query.exec()) { if (!query.exec()) {
@@ -190,7 +199,7 @@ bool ProfileRepository::updateProfile(const Profile& profile) const
QSqlQuery query(QSqlDatabase::database(m_connectionName)); QSqlQuery query(QSqlDatabase::database(m_connectionName));
query.prepare(QStringLiteral( query.prepare(QStringLiteral(
"UPDATE profiles " "UPDATE profiles "
"SET name = ?, host = ?, port = ?, username = ?, protocol = ?, auth_mode = ? " "SET name = ?, host = ?, port = ?, username = ?, protocol = ?, auth_mode = ?, private_key_path = ?, known_hosts_policy = ? "
"WHERE id = ?")); "WHERE id = ?"));
bindProfileFields(query, profile); bindProfileFields(query, profile);
query.addBindValue(profile.id); query.addBindValue(profile.id);
@@ -242,7 +251,9 @@ bool ProfileRepository::initializeDatabase()
"port INTEGER NOT NULL DEFAULT 22," "port INTEGER NOT NULL DEFAULT 22,"
"username TEXT NOT NULL DEFAULT ''," "username TEXT NOT NULL DEFAULT '',"
"protocol TEXT NOT NULL DEFAULT 'SSH'," "protocol TEXT NOT NULL DEFAULT 'SSH',"
"auth_mode TEXT NOT NULL DEFAULT 'Password'" "auth_mode TEXT NOT NULL DEFAULT 'Password',"
"private_key_path TEXT NOT NULL DEFAULT '',"
"known_hosts_policy TEXT NOT NULL DEFAULT 'Ask'"
")")); ")"));
if (!created) { if (!created) {
@@ -286,7 +297,9 @@ bool ProfileRepository::ensureProfileSchema() const
{QStringLiteral("port"), QStringLiteral("ALTER TABLE profiles ADD COLUMN port INTEGER NOT NULL DEFAULT 22")}, {QStringLiteral("port"), QStringLiteral("ALTER TABLE profiles ADD COLUMN port INTEGER NOT NULL DEFAULT 22")},
{QStringLiteral("username"), QStringLiteral("ALTER TABLE profiles ADD COLUMN username TEXT NOT NULL DEFAULT ''")}, {QStringLiteral("username"), QStringLiteral("ALTER TABLE profiles ADD COLUMN username TEXT NOT NULL DEFAULT ''")},
{QStringLiteral("protocol"), QStringLiteral("ALTER TABLE profiles ADD COLUMN protocol TEXT NOT NULL DEFAULT 'SSH'")}, {QStringLiteral("protocol"), QStringLiteral("ALTER TABLE profiles ADD COLUMN protocol TEXT NOT NULL DEFAULT 'SSH'")},
{QStringLiteral("auth_mode"), QStringLiteral("ALTER TABLE profiles ADD COLUMN auth_mode TEXT NOT NULL DEFAULT 'Password'")}}; {QStringLiteral("auth_mode"), QStringLiteral("ALTER TABLE profiles ADD COLUMN auth_mode TEXT NOT NULL DEFAULT 'Password'")},
{QStringLiteral("private_key_path"), QStringLiteral("ALTER TABLE profiles ADD COLUMN private_key_path TEXT NOT NULL DEFAULT ''")},
{QStringLiteral("known_hosts_policy"), QStringLiteral("ALTER TABLE profiles ADD COLUMN known_hosts_policy TEXT NOT NULL DEFAULT 'Ask'")}};
for (const ColumnDef& column : required) { for (const ColumnDef& column : required) {
if (columns.contains(column.name)) { if (columns.contains(column.name)) {

View File

@@ -16,6 +16,8 @@ struct Profile
QString username; QString username;
QString protocol = QStringLiteral("SSH"); QString protocol = QStringLiteral("SSH");
QString authMode = QStringLiteral("Password"); QString authMode = QStringLiteral("Password");
QString privateKeyPath;
QString knownHostsPolicy = QStringLiteral("Ask");
}; };
class ProfileRepository class ProfileRepository

View File

@@ -119,12 +119,13 @@ void ProfilesWindow::loadProfiles(const QString& query)
for (const Profile& profile : profiles) { for (const Profile& profile : profiles) {
auto* item = new QListWidgetItem(formatProfileListItem(profile), m_profilesList); auto* item = new QListWidgetItem(formatProfileListItem(profile), m_profilesList);
item->setData(Qt::UserRole, QVariant::fromValue(profile.id)); item->setData(Qt::UserRole, QVariant::fromValue(profile.id));
item->setToolTip(QStringLiteral("%1://%2@%3:%4\nAuth: %5") item->setToolTip(QStringLiteral("%1://%2@%3:%4\nAuth: %5\nKnown Hosts: %6")
.arg(profile.protocol, .arg(profile.protocol,
profile.username.isEmpty() ? QStringLiteral("<none>") : profile.username, profile.username.isEmpty() ? QStringLiteral("<none>") : profile.username,
profile.host, profile.host,
QString::number(profile.port), QString::number(profile.port),
profile.authMode)); profile.authMode,
profile.knownHostsPolicy));
m_profileCache.insert_or_assign(profile.id, profile); m_profileCache.insert_or_assign(profile.id, profile);
} }
} }

62
src/session_backend.h Normal file
View File

@@ -0,0 +1,62 @@
#ifndef ORBITHUB_SESSION_BACKEND_H
#define ORBITHUB_SESSION_BACKEND_H
#include "profile_repository.h"
#include <QObject>
#include <QString>
class SessionConnectOptions
{
public:
QString password;
QString privateKeyPath;
QString knownHostsPolicy;
};
enum class SessionState {
Disconnected,
Connecting,
Connected,
Failed,
};
class SessionBackend : public QObject
{
Q_OBJECT
public:
explicit SessionBackend(const Profile& profile, QObject* parent = nullptr)
: QObject(parent), m_profile(profile)
{
}
~SessionBackend() override = default;
const Profile& profile() const
{
return m_profile;
}
public slots:
virtual void connectSession(const SessionConnectOptions& options) = 0;
virtual void disconnectSession() = 0;
virtual void reconnectSession(const SessionConnectOptions& options) = 0;
virtual void sendInput(const QString& input) = 0;
virtual void confirmHostKey(bool trustHost) = 0;
virtual void updateTerminalSize(int columns, int rows) = 0;
signals:
void stateChanged(SessionState state, const QString& message);
void eventLogged(const QString& message);
void connectionError(const QString& displayMessage, const QString& rawMessage);
void outputReceived(const QString& text);
void hostKeyConfirmationRequested(const QString& prompt);
private:
Profile m_profile;
};
Q_DECLARE_METATYPE(SessionConnectOptions)
Q_DECLARE_METATYPE(SessionState)
#endif

View File

@@ -0,0 +1,14 @@
#include "session_backend_factory.h"
#include "session_backend.h"
#include "ssh_session_backend.h"
#include "unsupported_session_backend.h"
std::unique_ptr<SessionBackend> createSessionBackend(const Profile& profile)
{
if (profile.protocol.compare(QStringLiteral("SSH"), Qt::CaseInsensitive) == 0) {
return std::make_unique<SshSessionBackend>(profile);
}
return std::make_unique<UnsupportedSessionBackend>(profile);
}

View File

@@ -0,0 +1,12 @@
#ifndef ORBITHUB_SESSION_BACKEND_FACTORY_H
#define ORBITHUB_SESSION_BACKEND_FACTORY_H
#include "profile_repository.h"
#include <memory>
class SessionBackend;
std::unique_ptr<SessionBackend> createSessionBackend(const Profile& profile);
#endif

670
src/session_tab.cpp Normal file
View File

@@ -0,0 +1,670 @@
#include "session_tab.h"
#include "session_backend_factory.h"
#include "terminal_view.h"
#include <KodoTerm/KodoTerm.hpp>
#include <QDateTime>
#include <QFileDialog>
#include <QFileInfo>
#include <QFont>
#include <QFontDatabase>
#include <QHBoxLayout>
#include <QInputDialog>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QProcessEnvironment>
#include <QThread>
#include <QTimer>
#include <QToolButton>
#include <QVBoxLayout>
#include <memory>
namespace {
QFont defaultTerminalFont()
{
QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
font.setStyleHint(QFont::Monospace);
font.setFixedPitch(true);
font.setKerning(false);
font.setLetterSpacing(QFont::AbsoluteSpacing, 0.0);
return font;
}
TerminalTheme themeForName(const QString& themeName)
{
if (themeName.compare(QStringLiteral("Light"), Qt::CaseInsensitive) == 0) {
return TerminalTheme::loadKonsoleTheme(
QStringLiteral(":/KodoTermThemes/konsole/BlackOnWhite.colorscheme"));
}
if (themeName.compare(QStringLiteral("Solarized Dark"), Qt::CaseInsensitive) == 0) {
return TerminalTheme::loadKonsoleTheme(
QStringLiteral(":/KodoTermThemes/konsole/Solarized.colorscheme"));
}
return TerminalTheme::loadKonsoleTheme(
QStringLiteral(":/KodoTermThemes/konsole/Breeze.colorscheme"));
}
}
SessionTab::SessionTab(const Profile& profile, QWidget* parent)
: QWidget(parent),
m_profile(profile),
m_backendThread(nullptr),
m_backend(nullptr),
m_useKodoTermForSsh(profile.protocol.compare(QStringLiteral("SSH"), Qt::CaseInsensitive)
== 0),
m_state(SessionState::Disconnected),
m_terminalThemeName(QStringLiteral("Dark")),
m_sshTerminal(nullptr),
m_terminalOutput(nullptr),
m_eventLog(nullptr),
m_toggleEventsButton(nullptr),
m_eventsPanel(nullptr)
{
qRegisterMetaType<SessionConnectOptions>("SessionConnectOptions");
qRegisterMetaType<SessionState>("SessionState");
setupUi();
if (m_useKodoTermForSsh) {
connect(m_sshTerminal,
&KodoTerm::finished,
this,
[this](int exitCode, int) {
if (m_state == SessionState::Disconnected) {
return;
}
if (m_state == SessionState::Connected) {
if (exitCode != 0) {
appendEvent(QStringLiteral("SSH session closed with exit code %1.")
.arg(exitCode));
}
setState(SessionState::Disconnected,
QStringLiteral("SSH session closed."));
return;
}
if (exitCode == 0) {
setState(SessionState::Disconnected,
QStringLiteral("SSH session ended."));
return;
}
m_lastError = QStringLiteral("ssh exited with code %1").arg(exitCode);
appendEvent(QStringLiteral("Error: %1").arg(m_lastError));
setState(SessionState::Failed,
QStringLiteral("SSH session exited unexpectedly."));
});
connect(m_sshTerminal,
&KodoTerm::cwdChanged,
this,
[this](const QString& cwd) {
if (!cwd.trimmed().isEmpty()) {
appendEvent(QStringLiteral("Remote cwd: %1").arg(cwd));
}
});
} else {
m_backendThread = new QThread(this);
std::unique_ptr<SessionBackend> backend = createSessionBackend(m_profile);
m_backend = backend.release();
m_backend->moveToThread(m_backendThread);
connect(m_backendThread, &QThread::finished, m_backend, &QObject::deleteLater);
connect(this,
&SessionTab::requestConnect,
m_backend,
&SessionBackend::connectSession,
Qt::QueuedConnection);
connect(this,
&SessionTab::requestDisconnect,
m_backend,
&SessionBackend::disconnectSession,
Qt::QueuedConnection);
connect(this,
&SessionTab::requestReconnect,
m_backend,
&SessionBackend::reconnectSession,
Qt::QueuedConnection);
connect(this,
&SessionTab::requestInput,
m_backend,
&SessionBackend::sendInput,
Qt::QueuedConnection);
connect(this,
&SessionTab::requestHostKeyConfirmation,
m_backend,
&SessionBackend::confirmHostKey,
Qt::QueuedConnection);
connect(this,
&SessionTab::requestTerminalSize,
m_backend,
&SessionBackend::updateTerminalSize,
Qt::QueuedConnection);
connect(m_backend,
&SessionBackend::stateChanged,
this,
&SessionTab::onBackendStateChanged,
Qt::QueuedConnection);
connect(m_backend,
&SessionBackend::eventLogged,
this,
&SessionTab::onBackendEventLogged,
Qt::QueuedConnection);
connect(m_backend,
&SessionBackend::connectionError,
this,
&SessionTab::onBackendConnectionError,
Qt::QueuedConnection);
connect(m_backend,
&SessionBackend::outputReceived,
this,
&SessionTab::onBackendOutputReceived,
Qt::QueuedConnection);
connect(m_backend,
&SessionBackend::hostKeyConfirmationRequested,
this,
&SessionTab::onBackendHostKeyConfirmationRequested,
Qt::QueuedConnection);
m_backendThread->start();
}
setState(SessionState::Disconnected, QStringLiteral("Ready to connect."));
QTimer::singleShot(0, this, &SessionTab::connectSession);
}
SessionTab::~SessionTab()
{
if (m_useKodoTermForSsh && m_sshTerminal != nullptr && m_state != SessionState::Disconnected) {
m_sshTerminal->kill();
}
if (m_backend != nullptr && m_backendThread != nullptr && m_backendThread->isRunning()) {
QMetaObject::invokeMethod(m_backend, "disconnectSession", Qt::BlockingQueuedConnection);
m_backendThread->quit();
m_backendThread->wait(2000);
}
}
QString SessionTab::tabTitle() const
{
return QStringLiteral("%1 (%2)").arg(m_profile.name, stateSuffix());
}
void SessionTab::connectSession()
{
if (m_state == SessionState::Connecting || m_state == SessionState::Connected) {
return;
}
if (!validateProfileForConnect()) {
return;
}
const std::optional<SessionConnectOptions> options = buildConnectOptions();
if (!options.has_value()) {
return;
}
m_lastConnectOptions = options.value();
if (m_useKodoTermForSsh) {
if (!startSshTerminal(options.value())) {
return;
}
return;
}
emit requestConnect(options.value());
}
void SessionTab::disconnectSession()
{
if (m_state == SessionState::Disconnected) {
return;
}
if (m_useKodoTermForSsh) {
if (m_sshTerminal != nullptr) {
m_sshTerminal->kill();
}
setState(SessionState::Disconnected, QStringLiteral("Session disconnected."));
return;
}
emit requestDisconnect();
}
void SessionTab::reconnectSession()
{
if (!validateProfileForConnect()) {
return;
}
const std::optional<SessionConnectOptions> options = buildConnectOptions();
if (!options.has_value()) {
return;
}
m_lastConnectOptions = options.value();
if (m_useKodoTermForSsh) {
if (m_sshTerminal != nullptr) {
m_sshTerminal->kill();
}
QTimer::singleShot(50,
this,
[this, options]() { startSshTerminal(options.value()); });
return;
}
emit requestReconnect(options.value());
}
void SessionTab::clearTerminal()
{
if (m_useKodoTermForSsh && m_sshTerminal != nullptr) {
m_sshTerminal->clearScrollback();
m_sshTerminal->setFocus();
return;
}
if (m_terminalOutput != nullptr) {
m_terminalOutput->clear();
if (m_state == SessionState::Connected) {
emit requestInput(QStringLiteral("\x0c"));
}
m_terminalOutput->setFocus();
}
}
void SessionTab::setTerminalThemeName(const QString& themeName)
{
const QString normalized = themeName.trimmed();
if (normalized.isEmpty()) {
return;
}
if (m_terminalThemeName.compare(normalized, Qt::CaseInsensitive) == 0) {
return;
}
m_terminalThemeName = normalized;
applyTerminalTheme(m_terminalThemeName);
appendEvent(QStringLiteral("Terminal theme set to %1.").arg(m_terminalThemeName));
}
QString SessionTab::terminalThemeName() const
{
return m_terminalThemeName;
}
void SessionTab::onBackendStateChanged(SessionState state, const QString& message)
{
setState(state, message);
}
void SessionTab::onBackendEventLogged(const QString& message)
{
appendEvent(message);
}
void SessionTab::onBackendConnectionError(const QString& displayMessage, const QString& rawMessage)
{
m_lastError = rawMessage.isEmpty() ? displayMessage : rawMessage;
appendEvent(QStringLiteral("Error: %1").arg(displayMessage));
}
void SessionTab::onBackendOutputReceived(const QString& text)
{
if (text.isEmpty() || m_terminalOutput == nullptr) {
return;
}
m_terminalOutput->appendTerminalData(text);
}
void SessionTab::onBackendHostKeyConfirmationRequested(const QString& prompt)
{
const QString question = prompt.isEmpty()
? QStringLiteral("Unknown SSH host key. Do you trust this host?")
: prompt;
const QMessageBox::StandardButton reply = QMessageBox::question(
this,
QStringLiteral("SSH Host Key Confirmation"),
QStringLiteral("%1\n\nTrust and continue?").arg(question),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
emit requestHostKeyConfirmation(reply == QMessageBox::Yes);
}
void SessionTab::setupUi()
{
auto* rootLayout = new QVBoxLayout(this);
if (m_useKodoTermForSsh) {
m_sshTerminal = new KodoTerm(this);
const QFont terminalFont = defaultTerminalFont();
KodoTermConfig config = m_sshTerminal->getConfig();
config.font = terminalFont;
config.textAntialiasing = true;
config.maxScrollback = 12000;
m_sshTerminal->setConfig(config);
rootLayout->addWidget(m_sshTerminal, 1);
} else {
m_terminalOutput = new TerminalView(this);
m_terminalOutput->setFont(defaultTerminalFont());
m_terminalOutput->setMinimumHeight(260);
m_terminalOutput->setPlaceholderText(
QStringLiteral("Session is connecting. Type directly here once connected."));
rootLayout->addWidget(m_terminalOutput, 1);
}
applyTerminalTheme(m_terminalThemeName);
auto* eventsHeader = new QHBoxLayout();
m_toggleEventsButton = new QToolButton(this);
m_toggleEventsButton->setCheckable(true);
eventsHeader->addWidget(m_toggleEventsButton);
eventsHeader->addStretch();
m_eventsPanel = new QWidget(this);
auto* eventsLayout = new QVBoxLayout(m_eventsPanel);
eventsLayout->setContentsMargins(0, 0, 0, 0);
auto* eventTitle = new QLabel(QStringLiteral("Session Events"), m_eventsPanel);
m_eventLog = new QPlainTextEdit(m_eventsPanel);
m_eventLog->setReadOnly(true);
m_eventLog->setPlaceholderText(QStringLiteral("Session event log..."));
m_eventLog->setMinimumHeight(140);
eventsLayout->addWidget(eventTitle);
eventsLayout->addWidget(m_eventLog);
rootLayout->addLayout(eventsHeader);
rootLayout->addWidget(m_eventsPanel);
setPanelExpanded(m_toggleEventsButton, m_eventsPanel, QStringLiteral("Events"), false);
connect(m_toggleEventsButton,
&QToolButton::toggled,
this,
[this](bool expanded) {
setPanelExpanded(
m_toggleEventsButton, m_eventsPanel, QStringLiteral("Events"), expanded);
});
if (m_terminalOutput != nullptr) {
connect(m_terminalOutput,
&TerminalView::inputGenerated,
this,
[this](const QString& input) { emit requestInput(input); });
connect(m_terminalOutput,
&TerminalView::terminalSizeChanged,
this,
[this](int columns, int rows) { emit requestTerminalSize(columns, rows); });
}
}
std::optional<SessionConnectOptions> SessionTab::buildConnectOptions()
{
SessionConnectOptions options;
options.knownHostsPolicy = m_profile.knownHostsPolicy;
if (m_profile.protocol.compare(QStringLiteral("SSH"), Qt::CaseInsensitive) != 0) {
return options;
}
if (m_useKodoTermForSsh
&& m_profile.authMode.compare(QStringLiteral("Password"), Qt::CaseInsensitive) == 0) {
// Password is entered directly in terminal prompt.
return options;
}
if (m_profile.authMode.compare(QStringLiteral("Password"), Qt::CaseInsensitive) == 0) {
bool accepted = false;
const QString password = QInputDialog::getText(this,
QStringLiteral("SSH Password"),
QStringLiteral("Password for %1@%2:")
.arg(m_profile.username, m_profile.host),
QLineEdit::Password,
QString(),
&accepted);
if (!accepted) {
return std::nullopt;
}
if (password.isEmpty()) {
QMessageBox::warning(this,
QStringLiteral("Connect"),
QStringLiteral("Password is required for password authentication."));
return std::nullopt;
}
options.password = password;
return options;
}
QString keyPath = m_profile.privateKeyPath.trimmed();
if (keyPath.isEmpty()) {
keyPath = QFileDialog::getOpenFileName(this,
QStringLiteral("Select Private Key"),
QString(),
QStringLiteral("All Files (*)"));
if (keyPath.isEmpty()) {
return std::nullopt;
}
}
if (!QFileInfo::exists(keyPath)) {
QMessageBox::warning(this,
QStringLiteral("Connect"),
QStringLiteral("Private key file not found: %1").arg(keyPath));
return std::nullopt;
}
options.privateKeyPath = keyPath;
return options;
}
bool SessionTab::validateProfileForConnect()
{
if (m_profile.protocol.compare(QStringLiteral("SSH"), Qt::CaseInsensitive) != 0) {
return true;
}
if (m_profile.host.trimmed().isEmpty()) {
QMessageBox::warning(this,
QStringLiteral("Connect"),
QStringLiteral("SSH host is required."));
return false;
}
if (m_profile.username.trimmed().isEmpty()) {
QMessageBox::warning(this,
QStringLiteral("Connect"),
QStringLiteral("SSH username is required."));
return false;
}
if (m_profile.port < 1 || m_profile.port > 65535) {
QMessageBox::warning(this,
QStringLiteral("Connect"),
QStringLiteral("SSH port must be between 1 and 65535."));
return false;
}
return true;
}
void SessionTab::appendEvent(const QString& message)
{
const QString timestamp = QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd HH:mm:ss"));
m_eventLog->appendPlainText(QStringLiteral("[%1] %2").arg(timestamp, message));
}
void SessionTab::setState(SessionState state, const QString& message)
{
m_state = state;
appendEvent(QStringLiteral("Connection state: %1").arg(message));
refreshActionButtons();
emit tabTitleChanged(tabTitle());
emit tabStateChanged(state);
}
QString SessionTab::stateSuffix() const
{
switch (m_state) {
case SessionState::Disconnected:
return QStringLiteral("Disconnected");
case SessionState::Connecting:
return QStringLiteral("Connecting");
case SessionState::Connected:
return QStringLiteral("Connected");
case SessionState::Failed:
return QStringLiteral("Failed");
}
return QStringLiteral("Unknown");
}
void SessionTab::refreshActionButtons()
{
const bool isConnected = m_state == SessionState::Connected;
if (m_useKodoTermForSsh && m_sshTerminal != nullptr) {
m_sshTerminal->setEnabled(true);
m_sshTerminal->setFocus();
return;
}
if (m_terminalOutput != nullptr) {
m_terminalOutput->setEnabled(isConnected);
m_terminalOutput->setFocus();
}
}
void SessionTab::setPanelExpanded(QToolButton* button,
QWidget* panel,
const QString& name,
bool expanded)
{
if (button == nullptr || panel == nullptr) {
return;
}
button->blockSignals(true);
button->setChecked(expanded);
button->blockSignals(false);
panel->setVisible(expanded);
button->setText(expanded ? QStringLiteral("Hide %1").arg(name)
: QStringLiteral("Show %1").arg(name));
}
bool SessionTab::startSshTerminal(const SessionConnectOptions& options)
{
if (m_sshTerminal == nullptr) {
return false;
}
QStringList args;
args << QStringLiteral("-tt") << QStringLiteral("-p") << QString::number(m_profile.port)
<< QStringLiteral("-o") << QStringLiteral("ConnectTimeout=12") << QStringLiteral("-o")
<< QStringLiteral("ServerAliveInterval=20") << QStringLiteral("-o")
<< QStringLiteral("ServerAliveCountMax=2");
const QString policy = options.knownHostsPolicy.trimmed().isEmpty()
? m_profile.knownHostsPolicy.trimmed()
: options.knownHostsPolicy.trimmed();
if (policy.compare(QStringLiteral("Ignore"), Qt::CaseInsensitive) == 0) {
#ifdef Q_OS_WIN
const QString knownHostsNullDevice = QStringLiteral("NUL");
#else
const QString knownHostsNullDevice = QStringLiteral("/dev/null");
#endif
args << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=no")
<< QStringLiteral("-o")
<< QStringLiteral("UserKnownHostsFile=%1").arg(knownHostsNullDevice);
} else if (policy.compare(QStringLiteral("Accept New"), Qt::CaseInsensitive) == 0) {
args << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=accept-new");
} else if (policy.compare(QStringLiteral("Ask"), Qt::CaseInsensitive) == 0) {
args << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=ask");
} else {
args << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=yes");
}
if (m_profile.authMode.compare(QStringLiteral("Private Key"), Qt::CaseInsensitive) == 0) {
QString keyPath = options.privateKeyPath.trimmed();
if (keyPath.isEmpty()) {
keyPath = m_profile.privateKeyPath.trimmed();
}
if (keyPath.isEmpty()) {
m_lastError = QStringLiteral("Private key path is required.");
appendEvent(QStringLiteral("Error: %1").arg(m_lastError));
setState(SessionState::Failed, m_lastError);
return false;
}
args << QStringLiteral("-i") << keyPath;
}
const QString target = m_profile.username.trimmed().isEmpty()
? m_profile.host.trimmed()
: QStringLiteral("%1@%2").arg(m_profile.username.trimmed(), m_profile.host.trimmed());
args << target;
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
if (!env.contains(QStringLiteral("TERM"))) {
env.insert(QStringLiteral("TERM"), QStringLiteral("xterm-256color"));
}
if (!env.contains(QStringLiteral("COLORTERM"))) {
env.insert(QStringLiteral("COLORTERM"), QStringLiteral("truecolor"));
}
m_sshTerminal->setProgram(QStringLiteral("ssh"));
m_sshTerminal->setArguments(args);
m_sshTerminal->setProcessEnvironment(env);
appendEvent(QStringLiteral("Launching SSH terminal session."));
setState(SessionState::Connecting, QStringLiteral("Starting SSH terminal..."));
if (!m_sshTerminal->start()) {
m_lastError = QStringLiteral("Failed to start embedded SSH terminal process.");
appendEvent(QStringLiteral("Error: %1").arg(m_lastError));
setState(SessionState::Failed, QStringLiteral("Failed to start SSH terminal."));
return false;
}
setState(SessionState::Connected, QStringLiteral("SSH session established."));
return true;
}
void SessionTab::applyTerminalTheme(const QString& themeName)
{
if (m_useKodoTermForSsh) {
if (m_sshTerminal != nullptr) {
m_sshTerminal->setTheme(themeForName(themeName));
}
return;
}
if (m_terminalOutput != nullptr) {
m_terminalOutput->setThemeName(themeName);
}
}

79
src/session_tab.h Normal file
View File

@@ -0,0 +1,79 @@
#ifndef ORBITHUB_SESSION_TAB_H
#define ORBITHUB_SESSION_TAB_H
#include "profile_repository.h"
#include "session_backend.h"
#include <QWidget>
#include <optional>
class QPlainTextEdit;
class QThread;
class SessionBackend;
class TerminalView;
class QToolButton;
class KodoTerm;
class SessionTab : public QWidget
{
Q_OBJECT
public:
explicit SessionTab(const Profile& profile, QWidget* parent = nullptr);
~SessionTab() override;
QString tabTitle() const;
void connectSession();
void disconnectSession();
void reconnectSession();
void clearTerminal();
void setTerminalThemeName(const QString& themeName);
QString terminalThemeName() const;
signals:
void tabTitleChanged(const QString& title);
void tabStateChanged(SessionState state);
void requestConnect(const SessionConnectOptions& options);
void requestDisconnect();
void requestReconnect(const SessionConnectOptions& options);
void requestInput(const QString& input);
void requestHostKeyConfirmation(bool trustHost);
void requestTerminalSize(int columns, int rows);
private slots:
void onBackendStateChanged(SessionState state, const QString& message);
void onBackendEventLogged(const QString& message);
void onBackendConnectionError(const QString& displayMessage, const QString& rawMessage);
void onBackendOutputReceived(const QString& text);
void onBackendHostKeyConfirmationRequested(const QString& prompt);
private:
Profile m_profile;
QThread* m_backendThread;
SessionBackend* m_backend;
bool m_useKodoTermForSsh;
SessionState m_state;
QString m_lastError;
SessionConnectOptions m_lastConnectOptions;
QString m_terminalThemeName;
KodoTerm* m_sshTerminal;
TerminalView* m_terminalOutput;
QPlainTextEdit* m_eventLog;
QToolButton* m_toggleEventsButton;
QWidget* m_eventsPanel;
void setupUi();
std::optional<SessionConnectOptions> buildConnectOptions();
bool validateProfileForConnect();
void appendEvent(const QString& message);
void setState(SessionState state, const QString& message);
QString stateSuffix() const;
void refreshActionButtons();
void setPanelExpanded(QToolButton* button, QWidget* panel, const QString& name, bool expanded);
bool startSshTerminal(const SessionConnectOptions& options);
void applyTerminalTheme(const QString& themeName);
};
#endif

View File

@@ -1,17 +1,43 @@
#include "session_window.h" #include "session_window.h"
#include <QFont> #include "session_tab.h"
#include <QLabel>
#include <QAction>
#include <QColor>
#include <QMenu>
#include <QPalette>
#include <QStringList>
#include <QTabBar>
#include <QTabWidget> #include <QTabWidget>
#include <QTimer>
#include <QVBoxLayout> namespace {
#include <QWidget> QColor tabColorForState(SessionState state, const QPalette& palette)
{
switch (state) {
case SessionState::Disconnected:
return palette.color(QPalette::WindowText);
case SessionState::Connecting:
return QColor(QStringLiteral("#9a6700"));
case SessionState::Connected:
return QColor(QStringLiteral("#2e7d32"));
case SessionState::Failed:
return QColor(QStringLiteral("#c62828"));
}
return palette.color(QPalette::WindowText);
}
QStringList terminalThemeNames()
{
return {QStringLiteral("Dark"), QStringLiteral("Light"), QStringLiteral("Solarized Dark")};
}
}
SessionWindow::SessionWindow(const Profile& profile, QWidget* parent) SessionWindow::SessionWindow(const Profile& profile, QWidget* parent)
: QMainWindow(parent), m_tabs(new QTabWidget(this)) : QMainWindow(parent), m_tabs(new QTabWidget(this))
{ {
setWindowTitle(QStringLiteral("OrbitHub Session - %1").arg(profile.name)); setWindowTitle(QStringLiteral("OrbitHub Session - %1").arg(profile.name));
resize(980, 680); resize(1080, 760);
m_tabs->setTabsClosable(true); m_tabs->setTabsClosable(true);
connect(m_tabs, connect(m_tabs,
@@ -19,12 +45,61 @@ SessionWindow::SessionWindow(const Profile& profile, QWidget* parent)
this, this,
[this](int index) { [this](int index) {
QWidget* tab = m_tabs->widget(index); QWidget* tab = m_tabs->widget(index);
if (auto* sessionTab = qobject_cast<SessionTab*>(tab)) {
sessionTab->disconnectSession();
}
m_tabs->removeTab(index); m_tabs->removeTab(index);
delete tab; delete tab;
if (m_tabs->count() == 0) { if (m_tabs->count() == 0) {
close(); close();
} }
}); });
m_tabs->tabBar()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_tabs->tabBar(),
&QWidget::customContextMenuRequested,
this,
[this](const QPoint& pos) {
const int index = m_tabs->tabBar()->tabAt(pos);
if (index < 0) {
return;
}
auto* tab = qobject_cast<SessionTab*>(m_tabs->widget(index));
if (tab == nullptr) {
return;
}
QMenu menu(this);
QAction* disconnectAction = menu.addAction(QStringLiteral("Disconnect"));
QAction* reconnectAction = menu.addAction(QStringLiteral("Reconnect"));
menu.addSeparator();
QMenu* themeMenu = menu.addMenu(QStringLiteral("Theme"));
QList<QAction*> themeActions;
const QString currentTheme = tab->terminalThemeName();
for (const QString& themeName : terminalThemeNames()) {
QAction* themeAction = themeMenu->addAction(themeName);
themeAction->setCheckable(true);
themeAction->setChecked(
themeName.compare(currentTheme, Qt::CaseInsensitive) == 0);
themeActions.append(themeAction);
}
QAction* clearAction = menu.addAction(QStringLiteral("Clear"));
QAction* chosen = menu.exec(m_tabs->tabBar()->mapToGlobal(pos));
if (chosen == disconnectAction) {
tab->disconnectSession();
} else if (chosen == reconnectAction) {
tab->reconnectSession();
} else if (chosen == clearAction) {
tab->clearTerminal();
} else {
for (QAction* themeAction : themeActions) {
if (chosen == themeAction) {
tab->setTerminalThemeName(themeAction->text());
break;
}
}
}
});
setCentralWidget(m_tabs); setCentralWidget(m_tabs);
addSessionTab(profile); addSessionTab(profile);
@@ -32,64 +107,36 @@ SessionWindow::SessionWindow(const Profile& profile, QWidget* parent)
void SessionWindow::addSessionTab(const Profile& profile) void SessionWindow::addSessionTab(const Profile& profile)
{ {
auto* container = new QWidget(this); auto* tab = new SessionTab(profile, this);
auto* layout = new QVBoxLayout(container); const int index = m_tabs->addTab(tab, tab->tabTitle());
m_tabs->setCurrentIndex(index);
m_tabs->tabBar()->setTabTextColor(
index, tabColorForState(SessionState::Disconnected, m_tabs->palette()));
auto* profileLabel = new QLabel(QStringLiteral("Profile: %1").arg(profile.name), container); connect(tab,
auto* endpointLabel = new QLabel( &SessionTab::tabTitleChanged,
QStringLiteral("Endpoint: %1://%2@%3:%4") this,
.arg(profile.protocol, [this, tab](const QString& title) { updateTabTitle(tab, title); });
profile.username.isEmpty() ? QStringLiteral("<none>") : profile.username, connect(tab,
profile.host, &SessionTab::tabStateChanged,
QString::number(profile.port)), this,
container); [this, tab](SessionState state) {
auto* authModeLabel = new QLabel(QStringLiteral("Auth Mode: %1").arg(profile.authMode), container); for (int i = 0; i < m_tabs->count(); ++i) {
auto* statusLabel = new QLabel(QStringLiteral("Connection State: Connecting"), container); if (m_tabs->widget(i) == tab) {
auto* surfaceLabel = new QLabel(QStringLiteral("OrbitHub Native Surface"), container); m_tabs->tabBar()->setTabTextColor(
i, tabColorForState(state, m_tabs->palette()));
QFont profileFont = profileLabel->font(); return;
profileFont.setBold(true); }
profileLabel->setFont(profileFont); }
});
QFont surfaceFont = surfaceLabel->font(); }
surfaceFont.setPointSize(surfaceFont.pointSize() + 6);
surfaceFont.setBold(true); void SessionWindow::updateTabTitle(SessionTab* tab, const QString& title)
surfaceLabel->setFont(surfaceFont); {
for (int i = 0; i < m_tabs->count(); ++i) {
statusLabel->setStyleSheet( if (m_tabs->widget(i) == tab) {
QStringLiteral("border: 1px solid #a5a5a5; background-color: #fff3cd; padding: 6px;")); m_tabs->setTabText(i, title);
return;
surfaceLabel->setAlignment(Qt::AlignCenter); }
surfaceLabel->setMinimumHeight(220); }
surfaceLabel->setStyleSheet(
QStringLiteral("border: 1px solid #8a8a8a; background-color: #f5f5f5;"));
layout->addWidget(profileLabel);
layout->addWidget(endpointLabel);
layout->addWidget(authModeLabel);
layout->addWidget(statusLabel);
layout->addWidget(surfaceLabel, 1);
const int tabIndex = m_tabs->addTab(container, QStringLiteral("%1 (Connecting)").arg(profile.name));
QTimer::singleShot(900,
this,
[this, tabIndex, statusLabel, profile]() {
const bool shouldFail = profile.host.contains(QStringLiteral("fail"),
Qt::CaseInsensitive);
if (shouldFail) {
statusLabel->setText(QStringLiteral("Connection State: Failed"));
statusLabel->setStyleSheet(QStringLiteral(
"border: 1px solid #a94442; background-color: #f2dede; padding: 6px;"));
m_tabs->setTabText(tabIndex,
QStringLiteral("%1 (Failed)").arg(profile.name));
return;
}
statusLabel->setText(QStringLiteral("Connection State: Connected"));
statusLabel->setStyleSheet(QStringLiteral(
"border: 1px solid #3c763d; background-color: #dff0d8; padding: 6px;"));
m_tabs->setTabText(tabIndex,
QStringLiteral("%1 (Connected)").arg(profile.name));
});
} }

View File

@@ -6,6 +6,7 @@
#include <QMainWindow> #include <QMainWindow>
class QTabWidget; class QTabWidget;
class SessionTab;
class SessionWindow : public QMainWindow class SessionWindow : public QMainWindow
{ {
@@ -18,6 +19,7 @@ private:
QTabWidget* m_tabs; QTabWidget* m_tabs;
void addSessionTab(const Profile& profile); void addSessionTab(const Profile& profile);
void updateTabTitle(SessionTab* tab, const QString& title);
}; };
#endif #endif

538
src/ssh_session_backend.cpp Normal file
View File

@@ -0,0 +1,538 @@
#include "ssh_session_backend.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QProcessEnvironment>
#include <QTextStream>
#include <QUuid>
namespace {
QString escapeForShellSingleQuotes(const QString& value)
{
QString escaped = value;
escaped.replace(QStringLiteral("'"), QStringLiteral("'\"'\"'"));
return escaped;
}
}
SshSessionBackend::SshSessionBackend(const Profile& profile, QObject* parent)
: SessionBackend(profile, parent),
m_process(new QProcess(this)),
m_connectedProbeTimer(new QTimer(this)),
m_state(SessionState::Disconnected),
m_userInitiatedDisconnect(false),
m_reconnectPending(false),
m_waitingForPasswordPrompt(false),
m_waitingForHostKeyConfirmation(false),
m_passwordSubmitted(false),
m_terminalColumns(0),
m_terminalRows(0)
{
m_connectedProbeTimer->setSingleShot(true);
connect(m_process, &QProcess::started, this, &SshSessionBackend::onProcessStarted);
connect(m_process,
&QProcess::errorOccurred,
this,
&SshSessionBackend::onProcessErrorOccurred);
connect(m_process,
qOverload<int, QProcess::ExitStatus>(&QProcess::finished),
this,
&SshSessionBackend::onProcessFinished);
connect(m_process,
&QProcess::readyReadStandardOutput,
this,
&SshSessionBackend::onReadyReadStandardOutput);
connect(m_process,
&QProcess::readyReadStandardError,
this,
&SshSessionBackend::onReadyReadStandardError);
connect(m_connectedProbeTimer,
&QTimer::timeout,
this,
&SshSessionBackend::onConnectedProbeTimeout);
}
SshSessionBackend::~SshSessionBackend()
{
if (m_process->state() != QProcess::NotRunning) {
m_process->kill();
m_process->waitForFinished(500);
}
cleanupAskPassScript();
}
void SshSessionBackend::connectSession(const SessionConnectOptions& options)
{
if (m_state == SessionState::Connected || m_state == SessionState::Connecting) {
emit eventLogged(QStringLiteral("Connect skipped: session is already active."));
return;
}
m_userInitiatedDisconnect = false;
m_reconnectPending = false;
m_lastRawError.clear();
m_activeOptions = options;
m_waitingForPasswordPrompt = false;
m_waitingForHostKeyConfirmation = false;
m_passwordSubmitted = false;
if (!startSshProcess(options)) {
return;
}
setState(SessionState::Connecting, QStringLiteral("Connecting to SSH endpoint..."));
emit eventLogged(QStringLiteral("Launching ssh client."));
}
void SshSessionBackend::disconnectSession()
{
if (m_process->state() == QProcess::NotRunning) {
if (m_state != SessionState::Disconnected) {
setState(SessionState::Disconnected, QStringLiteral("Session is disconnected."));
}
return;
}
m_userInitiatedDisconnect = true;
emit eventLogged(QStringLiteral("Disconnect requested."));
m_connectedProbeTimer->stop();
m_process->terminate();
QTimer::singleShot(1500,
this,
[this]() {
if (m_process->state() != QProcess::NotRunning) {
emit eventLogged(QStringLiteral("Force-stopping ssh process."));
m_process->kill();
}
});
}
void SshSessionBackend::reconnectSession(const SessionConnectOptions& options)
{
emit eventLogged(QStringLiteral("Reconnect requested."));
if (m_process->state() == QProcess::NotRunning) {
connectSession(options);
return;
}
m_reconnectPending = true;
m_reconnectOptions = options;
m_userInitiatedDisconnect = true;
m_process->terminate();
}
void SshSessionBackend::sendInput(const QString& input)
{
if (m_process->state() != QProcess::Running) {
emit eventLogged(QStringLiteral("Input ignored: session is not running."));
return;
}
if (input.isEmpty()) {
return;
}
m_process->write(input.toUtf8());
}
void SshSessionBackend::confirmHostKey(bool trustHost)
{
if (m_process->state() != QProcess::Running || !m_waitingForHostKeyConfirmation) {
return;
}
m_waitingForHostKeyConfirmation = false;
const QString response = trustHost ? QStringLiteral("yes\n") : QStringLiteral("no\n");
m_process->write(response.toUtf8());
emit eventLogged(trustHost
? QStringLiteral("Host key accepted by user.")
: QStringLiteral("Host key rejected by user."));
}
void SshSessionBackend::updateTerminalSize(int columns, int rows)
{
m_terminalColumns = columns;
m_terminalRows = rows;
if (m_state == SessionState::Connected) {
applyTerminalSizeIfAvailable();
}
}
void SshSessionBackend::onProcessStarted()
{
emit eventLogged(QStringLiteral("ssh process started."));
m_connectedProbeTimer->start(1200);
}
void SshSessionBackend::onProcessErrorOccurred(QProcess::ProcessError)
{
const QString rawError = m_process->errorString();
if (!rawError.isEmpty()) {
m_lastRawError += rawError + QLatin1Char('\n');
}
if (m_state == SessionState::Connecting) {
const QString display = mapSshError(m_lastRawError);
setState(SessionState::Failed, display);
emit connectionError(display, m_lastRawError.trimmed());
}
}
void SshSessionBackend::onProcessFinished(int exitCode, QProcess::ExitStatus)
{
m_connectedProbeTimer->stop();
cleanupAskPassScript();
if (m_reconnectPending) {
m_reconnectPending = false;
SessionConnectOptions options = m_reconnectOptions;
setState(SessionState::Disconnected, QStringLiteral("Reconnecting..."));
QTimer::singleShot(0, this, [this, options]() { connectSession(options); });
return;
}
if (m_userInitiatedDisconnect) {
m_userInitiatedDisconnect = false;
setState(SessionState::Disconnected, QStringLiteral("Session disconnected."));
emit eventLogged(QStringLiteral("ssh process exited after disconnect request."));
return;
}
if (m_state == SessionState::Connecting || exitCode != 0) {
QString rawError = m_lastRawError.trimmed();
if (rawError.isEmpty()) {
rawError = QStringLiteral("ssh exited with code %1").arg(exitCode);
}
const QString display = mapSshError(rawError);
setState(SessionState::Failed, display);
emit connectionError(display, rawError);
return;
}
setState(SessionState::Disconnected, QStringLiteral("SSH session ended."));
}
void SshSessionBackend::onReadyReadStandardOutput()
{
const QString chunk = QString::fromUtf8(m_process->readAllStandardOutput());
if (chunk.isEmpty()) {
return;
}
emit outputReceived(chunk);
if (m_state == SessionState::Connecting && !m_waitingForHostKeyConfirmation
&& !m_waitingForPasswordPrompt) {
setState(SessionState::Connected, QStringLiteral("SSH session established."));
}
}
void SshSessionBackend::onReadyReadStandardError()
{
const QString chunk = QString::fromUtf8(m_process->readAllStandardError());
if (chunk.isEmpty()) {
return;
}
m_lastRawError += chunk;
emit outputReceived(chunk);
const QStringList lines = chunk.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
for (const QString& line : lines) {
const QString trimmed = line.trimmed();
if (!trimmed.isEmpty()) {
emit eventLogged(trimmed);
}
if (trimmed.contains(QStringLiteral("Are you sure you want to continue connecting"),
Qt::CaseInsensitive)
&& !m_waitingForHostKeyConfirmation) {
m_waitingForHostKeyConfirmation = true;
emit eventLogged(QStringLiteral("Awaiting host key confirmation from user."));
emit hostKeyConfirmationRequested(trimmed);
continue;
}
if (trimmed.contains(QStringLiteral("password:"), Qt::CaseInsensitive)
&& profile().authMode.compare(QStringLiteral("Password"), Qt::CaseInsensitive) == 0
&& !m_passwordSubmitted) {
if (m_activeOptions.password.isEmpty()) {
const QString message = QStringLiteral("Password prompt received but no password is available.");
setState(SessionState::Failed, message);
emit connectionError(message, trimmed);
return;
}
m_waitingForPasswordPrompt = false;
m_passwordSubmitted = true;
m_process->write((m_activeOptions.password + QStringLiteral("\n")).toUtf8());
emit eventLogged(QStringLiteral("Password prompt received; credentials submitted."));
continue;
}
}
}
void SshSessionBackend::onConnectedProbeTimeout()
{
if (m_state != SessionState::Connecting) {
return;
}
if (m_process->state() == QProcess::Running && !m_waitingForHostKeyConfirmation
&& !m_waitingForPasswordPrompt) {
setState(SessionState::Connected, QStringLiteral("SSH session established."));
}
}
void SshSessionBackend::setState(SessionState state, const QString& message)
{
m_state = state;
emit stateChanged(state, message);
emit eventLogged(message);
if (m_state == SessionState::Connected) {
applyTerminalSizeIfAvailable();
}
}
bool SshSessionBackend::startSshProcess(const SessionConnectOptions& options)
{
const Profile& p = profile();
if (p.host.trimmed().isEmpty()) {
const QString message = QStringLiteral("Host is required for SSH connections.");
setState(SessionState::Failed, message);
emit connectionError(message, message);
return false;
}
if (p.port < 1 || p.port > 65535) {
const QString message = QStringLiteral("Port must be between 1 and 65535.");
setState(SessionState::Failed, message);
emit connectionError(message, message);
return false;
}
QStringList args;
args << QStringLiteral("-tt") << QStringLiteral("-p") << QString::number(p.port)
<< QStringLiteral("-o") << QStringLiteral("ConnectTimeout=12") << QStringLiteral("-o")
<< QStringLiteral("ServerAliveInterval=20") << QStringLiteral("-o")
<< QStringLiteral("ServerAliveCountMax=2");
const QString policy = options.knownHostsPolicy.trimmed().isEmpty()
? p.knownHostsPolicy.trimmed()
: options.knownHostsPolicy.trimmed();
if (policy.compare(QStringLiteral("Ignore"), Qt::CaseInsensitive) == 0) {
args << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=no")
<< QStringLiteral("-o")
<< QStringLiteral("UserKnownHostsFile=%1").arg(knownHostsFileForNullDevice());
} else if (policy.compare(QStringLiteral("Accept New"), Qt::CaseInsensitive) == 0) {
args << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=accept-new");
} else if (policy.compare(QStringLiteral("Ask"), Qt::CaseInsensitive) == 0) {
args << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=ask");
} else {
args << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=yes");
}
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
if (p.authMode.compare(QStringLiteral("Password"), Qt::CaseInsensitive) == 0) {
if (options.password.isEmpty()) {
const QString message = QStringLiteral("Password is required for password authentication.");
setState(SessionState::Failed, message);
emit connectionError(message, message);
return false;
}
args << QStringLiteral("-o") << QStringLiteral("PreferredAuthentications=password")
<< QStringLiteral("-o") << QStringLiteral("PubkeyAuthentication=no")
<< QStringLiteral("-o") << QStringLiteral("NumberOfPasswordPrompts=1");
m_waitingForPasswordPrompt = false;
QString askPassError;
if (!configureAskPass(options, environment, askPassError)) {
setState(SessionState::Failed, askPassError);
emit connectionError(askPassError, askPassError);
return false;
}
} else if (p.authMode.compare(QStringLiteral("Private Key"), Qt::CaseInsensitive) == 0) {
QString keyPath = options.privateKeyPath.trimmed();
if (keyPath.isEmpty()) {
keyPath = p.privateKeyPath.trimmed();
}
if (keyPath.isEmpty()) {
const QString message = QStringLiteral("Private key path is required.");
setState(SessionState::Failed, message);
emit connectionError(message, message);
return false;
}
if (!QFileInfo::exists(keyPath)) {
const QString message = QStringLiteral("Private key file does not exist: %1")
.arg(keyPath);
setState(SessionState::Failed, message);
emit connectionError(message, message);
return false;
}
args << QStringLiteral("-i") << keyPath << QStringLiteral("-o")
<< QStringLiteral("PreferredAuthentications=publickey") << QStringLiteral("-o")
<< QStringLiteral("PasswordAuthentication=no");
}
const QString target = p.username.trimmed().isEmpty()
? p.host.trimmed()
: QStringLiteral("%1@%2").arg(p.username.trimmed(), p.host.trimmed());
args << target;
m_process->setProcessEnvironment(environment);
m_process->setProgram(QStringLiteral("ssh"));
m_process->setArguments(args);
m_process->setProcessChannelMode(QProcess::SeparateChannels);
m_process->start();
if (!m_process->waitForStarted(3000)) {
const QString rawError = m_process->errorString();
const QString display = mapSshError(rawError);
setState(SessionState::Failed, display);
emit connectionError(display, rawError);
return false;
}
return true;
}
bool SshSessionBackend::configureAskPass(const SessionConnectOptions& options,
QProcessEnvironment& environment,
QString& error)
{
cleanupAskPassScript();
#ifdef Q_OS_WIN
m_askPassScriptPath = QDir::temp().filePath(
QStringLiteral("orbithub_askpass_%1.cmd")
.arg(QUuid::createUuid().toString(QUuid::WithoutBraces)));
#else
m_askPassScriptPath = QDir::temp().filePath(
QStringLiteral("orbithub_askpass_%1.sh")
.arg(QUuid::createUuid().toString(QUuid::WithoutBraces)));
#endif
QFile script(m_askPassScriptPath);
if (!script.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
error = QStringLiteral("Failed to create temporary askpass helper script.");
cleanupAskPassScript();
return false;
}
QTextStream out(&script);
#ifdef Q_OS_WIN
out << "@echo off\r\n";
out << "echo " << options.password << "\r\n";
#else
const QString escapedPassword = escapeForShellSingleQuotes(options.password);
out << "#!/bin/sh\n";
out << "printf '%s\\n' '" << escapedPassword << "'\n";
#endif
out.flush();
script.close();
#ifndef Q_OS_WIN
if (!QFile::setPermissions(m_askPassScriptPath,
QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)) {
error = QStringLiteral("Failed to set permissions on askpass helper script.");
cleanupAskPassScript();
return false;
}
#endif
environment.insert(QStringLiteral("SSH_ASKPASS"), m_askPassScriptPath);
environment.insert(QStringLiteral("SSH_ASKPASS_REQUIRE"), QStringLiteral("force"));
if (!environment.contains(QStringLiteral("DISPLAY"))) {
environment.insert(QStringLiteral("DISPLAY"), QStringLiteral(":0"));
}
return true;
}
void SshSessionBackend::cleanupAskPassScript()
{
if (!m_askPassScriptPath.isEmpty()) {
QFile::remove(m_askPassScriptPath);
m_askPassScriptPath.clear();
}
}
QString SshSessionBackend::mapSshError(const QString& rawError) const
{
const QString raw = rawError.trimmed();
if (raw.contains(QStringLiteral("Permission denied"), Qt::CaseInsensitive)) {
return QStringLiteral("Authentication failed. Check username and credentials.");
}
if (raw.contains(QStringLiteral("Host key verification failed"), Qt::CaseInsensitive)) {
return QStringLiteral("Host key verification failed.");
}
if (raw.contains(QStringLiteral("Could not resolve hostname"), Qt::CaseInsensitive)) {
return QStringLiteral("Host could not be resolved.");
}
if (raw.contains(QStringLiteral("Connection timed out"), Qt::CaseInsensitive)
|| raw.contains(QStringLiteral("Operation timed out"), Qt::CaseInsensitive)) {
return QStringLiteral("Connection timed out.");
}
if (raw.contains(QStringLiteral("Connection refused"), Qt::CaseInsensitive)) {
return QStringLiteral("Connection refused by remote host.");
}
if (raw.contains(QStringLiteral("No route to host"), Qt::CaseInsensitive)) {
return QStringLiteral("No route to host.");
}
if (raw.contains(QStringLiteral("Identity file"), Qt::CaseInsensitive)
&& raw.contains(QStringLiteral("not accessible"), Qt::CaseInsensitive)) {
return QStringLiteral("Private key file is not accessible.");
}
if (raw.contains(QStringLiteral("No such file or directory"), Qt::CaseInsensitive)) {
if (raw.contains(QStringLiteral("ssh-askpass"), Qt::CaseInsensitive)) {
return QStringLiteral("SSH password helper is missing or failed to launch.");
}
return QStringLiteral("Required file was not found.");
}
if (raw.isEmpty()) {
return QStringLiteral("SSH connection failed for an unknown reason.");
}
return QStringLiteral("SSH connection failed.");
}
QString SshSessionBackend::knownHostsFileForNullDevice() const
{
#ifdef Q_OS_WIN
return QStringLiteral("NUL");
#else
return QStringLiteral("/dev/null");
#endif
}
void SshSessionBackend::applyTerminalSizeIfAvailable()
{
if (m_process->state() != QProcess::Running) {
return;
}
if (m_terminalColumns <= 0 || m_terminalRows <= 0) {
return;
}
const QString command = QStringLiteral("stty cols %1 rows %2\\n")
.arg(m_terminalColumns)
.arg(m_terminalRows);
m_process->write(command.toUtf8());
emit eventLogged(
QStringLiteral("Applied terminal size: %1x%2").arg(m_terminalColumns).arg(m_terminalRows));
}

61
src/ssh_session_backend.h Normal file
View File

@@ -0,0 +1,61 @@
#ifndef ORBITHUB_SSH_SESSION_BACKEND_H
#define ORBITHUB_SSH_SESSION_BACKEND_H
#include "session_backend.h"
#include <QProcess>
#include <QString>
#include <QTimer>
class SshSessionBackend : public SessionBackend
{
Q_OBJECT
public:
explicit SshSessionBackend(const Profile& profile, QObject* parent = nullptr);
~SshSessionBackend() override;
public slots:
void connectSession(const SessionConnectOptions& options) override;
void disconnectSession() override;
void reconnectSession(const SessionConnectOptions& options) override;
void sendInput(const QString& input) override;
void confirmHostKey(bool trustHost) override;
void updateTerminalSize(int columns, int rows) override;
private slots:
void onProcessStarted();
void onProcessErrorOccurred(QProcess::ProcessError error);
void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
void onReadyReadStandardOutput();
void onReadyReadStandardError();
void onConnectedProbeTimeout();
private:
QProcess* m_process;
QTimer* m_connectedProbeTimer;
SessionState m_state;
bool m_userInitiatedDisconnect;
bool m_reconnectPending;
SessionConnectOptions m_reconnectOptions;
SessionConnectOptions m_activeOptions;
QString m_lastRawError;
QString m_askPassScriptPath;
bool m_waitingForPasswordPrompt;
bool m_waitingForHostKeyConfirmation;
bool m_passwordSubmitted;
int m_terminalColumns;
int m_terminalRows;
void setState(SessionState state, const QString& message);
bool startSshProcess(const SessionConnectOptions& options);
bool configureAskPass(const SessionConnectOptions& options,
QProcessEnvironment& environment,
QString& error);
void cleanupAskPassScript();
QString mapSshError(const QString& rawError) const;
QString knownHostsFileForNullDevice() const;
void applyTerminalSizeIfAvailable();
};
#endif

520
src/terminal_view.cpp Normal file
View File

@@ -0,0 +1,520 @@
#include "terminal_view.h"
#include <QApplication>
#include <QClipboard>
#include <QColor>
#include <QFocusEvent>
#include <QFontMetrics>
#include <QKeyEvent>
#include <QResizeEvent>
#include <QTimer>
#include <QTextCursor>
#include <algorithm>
namespace {
QString normalizedThemeName(const QString& value)
{
return value.trimmed().toLower();
}
}
TerminalView::TerminalView(QWidget* parent)
: QTextEdit(parent),
m_bold(false),
m_hasFgColor(false),
m_hasBgColor(false)
{
setReadOnly(false);
setUndoRedoEnabled(false);
setAcceptRichText(false);
setLineWrapMode(QTextEdit::NoWrap);
setContextMenuPolicy(Qt::NoContextMenu);
setCursorWidth(2);
document()->setMaximumBlockCount(4000);
applyThemePalette(paletteByName(QStringLiteral("Dark")));
resetSgrState();
QTimer::singleShot(0, this, [this]() {
moveCursor(QTextCursor::End);
emitTerminalSize();
});
}
QStringList TerminalView::themeNames()
{
return {QStringLiteral("Dark"), QStringLiteral("Light"), QStringLiteral("Solarized Dark")};
}
void TerminalView::setThemeName(const QString& themeName)
{
applyThemePalette(paletteByName(themeName));
}
void TerminalView::appendTerminalData(const QString& data)
{
if (data.isEmpty()) {
return;
}
const QString merged = m_pendingEscape + data;
m_pendingEscape.clear();
QString plainBuffer;
for (int i = 0; i < merged.size();) {
const QChar ch = merged.at(i);
if (ch == QChar::fromLatin1('\x1b')) {
if (!plainBuffer.isEmpty()) {
appendTextChunk(plainBuffer);
plainBuffer.clear();
}
if (i + 1 >= merged.size()) {
m_pendingEscape = merged.mid(i);
break;
}
if (merged.at(i + 1) != QChar::fromLatin1('[')) {
i += 2;
continue;
}
int end = i + 2;
while (end < merged.size()) {
const ushort c = merged.at(end).unicode();
if (c >= 0x40 && c <= 0x7e) {
break;
}
++end;
}
if (end >= merged.size()) {
m_pendingEscape = merged.mid(i);
break;
}
const QChar finalByte = merged.at(end);
const QString params = merged.mid(i + 2, end - (i + 2));
if (finalByte == QChar::fromLatin1('m')) {
handleSgrSequence(params);
} else if (finalByte == QChar::fromLatin1('J')) {
if (params.isEmpty() || params == QStringLiteral("2")) {
clear();
}
}
i = end + 1;
continue;
}
if (ch == QChar::fromLatin1('\r')) {
const bool hasLfAfter = (i + 1 < merged.size() && merged.at(i + 1) == QChar::fromLatin1('\n'));
if (!hasLfAfter) {
plainBuffer.append(QChar::fromLatin1('\n'));
}
++i;
continue;
}
plainBuffer.append(ch);
++i;
}
if (!plainBuffer.isEmpty()) {
appendTextChunk(plainBuffer);
}
}
void TerminalView::keyPressEvent(QKeyEvent* event)
{
if (event == nullptr) {
return;
}
moveCursor(QTextCursor::End);
const Qt::KeyboardModifiers modifiers = event->modifiers();
if (modifiers == (Qt::ControlModifier | Qt::ShiftModifier)
&& event->key() == Qt::Key_C) {
const QString selected = textCursor().selectedText();
if (!selected.isEmpty()) {
QApplication::clipboard()->setText(selected);
}
return;
}
if (modifiers == Qt::ControlModifier) {
switch (event->key()) {
case Qt::Key_C:
emit inputGenerated(QStringLiteral("\x03"));
return;
case Qt::Key_D:
emit inputGenerated(QStringLiteral("\x04"));
return;
case Qt::Key_L:
emit inputGenerated(QStringLiteral("\x0c"));
return;
case Qt::Key_V: {
const QString clipboardText = QApplication::clipboard()->text();
if (!clipboardText.isEmpty()) {
emit inputGenerated(clipboardText);
}
return;
}
default:
break;
}
}
switch (event->key()) {
case Qt::Key_Return:
case Qt::Key_Enter:
emit inputGenerated(QStringLiteral("\n"));
return;
case Qt::Key_Backspace:
emit inputGenerated(QStringLiteral("\x7f"));
return;
case Qt::Key_Tab:
emit inputGenerated(QStringLiteral("\t"));
return;
case Qt::Key_Left:
emit inputGenerated(QStringLiteral("\x1b[D"));
return;
case Qt::Key_Right:
emit inputGenerated(QStringLiteral("\x1b[C"));
return;
case Qt::Key_Up:
emit inputGenerated(QStringLiteral("\x1b[A"));
return;
case Qt::Key_Down:
emit inputGenerated(QStringLiteral("\x1b[B"));
return;
default:
break;
}
const QString text = event->text();
if (!text.isEmpty()) {
emit inputGenerated(text);
return;
}
}
void TerminalView::focusInEvent(QFocusEvent* event)
{
QTextEdit::focusInEvent(event);
moveCursor(QTextCursor::End);
}
void TerminalView::resizeEvent(QResizeEvent* event)
{
QTextEdit::resizeEvent(event);
emitTerminalSize();
}
TerminalView::ThemePalette TerminalView::paletteByName(const QString& themeName)
{
const QString theme = normalizedThemeName(themeName);
if (theme == QStringLiteral("light")) {
return ThemePalette{QStringLiteral("Light"),
QColor(QStringLiteral("#ececec")),
QColor(QStringLiteral("#000000")),
{QColor(QStringLiteral("#000000")),
QColor(QStringLiteral("#aa0000")),
QColor(QStringLiteral("#008000")),
QColor(QStringLiteral("#7a5f00")),
QColor(QStringLiteral("#0033cc")),
QColor(QStringLiteral("#8a00a8")),
QColor(QStringLiteral("#005f87")),
QColor(QStringLiteral("#333333"))},
{QColor(QStringLiteral("#5c5c5c")),
QColor(QStringLiteral("#d30000")),
QColor(QStringLiteral("#00a000")),
QColor(QStringLiteral("#9a7700")),
QColor(QStringLiteral("#0055ff")),
QColor(QStringLiteral("#b300db")),
QColor(QStringLiteral("#007ea7")),
QColor(QStringLiteral("#111111"))}};
}
if (theme == QStringLiteral("solarized dark")) {
return ThemePalette{QStringLiteral("Solarized Dark"),
QColor(QStringLiteral("#002b36")),
QColor(QStringLiteral("#839496")),
{QColor(QStringLiteral("#073642")),
QColor(QStringLiteral("#dc322f")),
QColor(QStringLiteral("#859900")),
QColor(QStringLiteral("#b58900")),
QColor(QStringLiteral("#268bd2")),
QColor(QStringLiteral("#d33682")),
QColor(QStringLiteral("#2aa198")),
QColor(QStringLiteral("#eee8d5"))},
{QColor(QStringLiteral("#586e75")),
QColor(QStringLiteral("#cb4b16")),
QColor(QStringLiteral("#586e75")),
QColor(QStringLiteral("#657b83")),
QColor(QStringLiteral("#839496")),
QColor(QStringLiteral("#6c71c4")),
QColor(QStringLiteral("#93a1a1")),
QColor(QStringLiteral("#fdf6e3"))}};
}
return ThemePalette{QStringLiteral("Dark"),
QColor(QStringLiteral("#1e1e1e")),
QColor(QStringLiteral("#d4d4d4")),
{QColor(QStringLiteral("#000000")),
QColor(QStringLiteral("#cd3131")),
QColor(QStringLiteral("#0dbc79")),
QColor(QStringLiteral("#e5e510")),
QColor(QStringLiteral("#2472c8")),
QColor(QStringLiteral("#bc3fbc")),
QColor(QStringLiteral("#11a8cd")),
QColor(QStringLiteral("#e5e5e5"))},
{QColor(QStringLiteral("#666666")),
QColor(QStringLiteral("#f14c4c")),
QColor(QStringLiteral("#23d18b")),
QColor(QStringLiteral("#f5f543")),
QColor(QStringLiteral("#3b8eea")),
QColor(QStringLiteral("#d670d6")),
QColor(QStringLiteral("#29b8db")),
QColor(QStringLiteral("#ffffff"))}};
}
QColor TerminalView::colorFrom256Index(int index)
{
if (index < 0) {
index = 0;
}
if (index > 255) {
index = 255;
}
if (index < 16) {
static const std::array<QColor, 16> base = {
QColor(QStringLiteral("#000000")), QColor(QStringLiteral("#800000")),
QColor(QStringLiteral("#008000")), QColor(QStringLiteral("#808000")),
QColor(QStringLiteral("#000080")), QColor(QStringLiteral("#800080")),
QColor(QStringLiteral("#008080")), QColor(QStringLiteral("#c0c0c0")),
QColor(QStringLiteral("#808080")), QColor(QStringLiteral("#ff0000")),
QColor(QStringLiteral("#00ff00")), QColor(QStringLiteral("#ffff00")),
QColor(QStringLiteral("#0000ff")), QColor(QStringLiteral("#ff00ff")),
QColor(QStringLiteral("#00ffff")), QColor(QStringLiteral("#ffffff"))};
return base.at(static_cast<size_t>(index));
}
if (index >= 16 && index <= 231) {
const int c = index - 16;
const int r = c / 36;
const int g = (c / 6) % 6;
const int b = c % 6;
const auto channel = [](int v) { return v == 0 ? 0 : 55 + v * 40; };
return QColor(channel(r), channel(g), channel(b));
}
const int gray = 8 + (index - 232) * 10;
return QColor(gray, gray, gray);
}
void TerminalView::applyThemePalette(const ThemePalette& palette)
{
m_palette = palette;
const QString stylesheet = QStringLiteral("QTextEdit { background: %1; color: %2; }")
.arg(m_palette.background.name(), m_palette.foreground.name());
setStyleSheet(stylesheet);
if (!m_hasFgColor) {
m_fgColor = m_palette.foreground;
}
if (!m_hasBgColor) {
m_bgColor = m_palette.background;
}
applyCurrentFormat();
}
void TerminalView::applyCurrentFormat()
{
m_currentFormat = QTextCharFormat();
m_currentFormat.setForeground(m_hasFgColor ? m_fgColor : m_palette.foreground);
if (m_hasBgColor) {
m_currentFormat.setBackground(m_bgColor);
}
QFont font = currentFont();
font.setBold(m_bold);
m_currentFormat.setFont(font);
}
void TerminalView::resetSgrState()
{
m_bold = false;
m_hasFgColor = false;
m_hasBgColor = false;
m_fgColor = m_palette.foreground;
m_bgColor = m_palette.background;
applyCurrentFormat();
}
void TerminalView::handleSgrSequence(const QString& params)
{
QStringList parts = params.split(QChar::fromLatin1(';'), Qt::KeepEmptyParts);
if (parts.isEmpty()) {
parts.push_back(QStringLiteral("0"));
}
for (int i = 0; i < parts.size(); ++i) {
const QString part = parts.at(i).trimmed();
bool ok = false;
const int code = part.isEmpty() ? 0 : part.toInt(&ok);
if (!ok && !part.isEmpty()) {
continue;
}
if (code == 0) {
resetSgrState();
continue;
}
if (code == 1) {
m_bold = true;
continue;
}
if (code == 22) {
m_bold = false;
continue;
}
if (code == 39) {
m_hasFgColor = false;
continue;
}
if (code == 49) {
m_hasBgColor = false;
continue;
}
if (code >= 30 && code <= 37) {
m_fgColor = paletteColor(false, code - 30, false);
m_hasFgColor = true;
continue;
}
if (code >= 90 && code <= 97) {
m_fgColor = paletteColor(false, code - 90, true);
m_hasFgColor = true;
continue;
}
if (code >= 40 && code <= 47) {
m_bgColor = paletteColor(true, code - 40, false);
m_hasBgColor = true;
continue;
}
if (code >= 100 && code <= 107) {
m_bgColor = paletteColor(true, code - 100, true);
m_hasBgColor = true;
continue;
}
if (code == 38 || code == 48) {
const bool background = (code == 48);
if (i + 1 >= parts.size()) {
continue;
}
const int mode = parts.at(i + 1).toInt(&ok);
if (!ok) {
continue;
}
if (mode == 5 && i + 2 < parts.size()) {
const int index = parts.at(i + 2).toInt(&ok);
if (ok) {
const QColor color = colorFrom256Index(index);
if (background) {
m_bgColor = color;
m_hasBgColor = true;
} else {
m_fgColor = color;
m_hasFgColor = true;
}
}
i += 2;
continue;
}
if (mode == 2 && i + 4 < parts.size()) {
const int r = parts.at(i + 2).toInt(&ok);
if (!ok) {
i += 4;
continue;
}
const int g = parts.at(i + 3).toInt(&ok);
if (!ok) {
i += 4;
continue;
}
const int b = parts.at(i + 4).toInt(&ok);
if (!ok) {
i += 4;
continue;
}
const QColor color(r, g, b);
if (background) {
m_bgColor = color;
m_hasBgColor = true;
} else {
m_fgColor = color;
m_hasFgColor = true;
}
i += 4;
continue;
}
}
}
applyCurrentFormat();
}
void TerminalView::appendTextChunk(const QString& text)
{
if (text.isEmpty()) {
return;
}
QTextCursor cursor = textCursor();
cursor.movePosition(QTextCursor::End);
cursor.insertText(text, m_currentFormat);
setTextCursor(cursor);
ensureCursorVisible();
}
QColor TerminalView::paletteColor(bool, int index, bool bright) const
{
const int safeIndex = std::clamp(index, 0, 7);
return bright ? m_palette.bright.at(static_cast<size_t>(safeIndex))
: m_palette.normal.at(static_cast<size_t>(safeIndex));
}
int TerminalView::terminalColumns() const
{
const QFontMetrics metrics(font());
const int cellWidth = std::max(1, metrics.horizontalAdvance(QChar::fromLatin1('M')));
return std::max(1, viewport()->width() / cellWidth);
}
int TerminalView::terminalRows() const
{
const QFontMetrics metrics(font());
const int cellHeight = std::max(1, metrics.lineSpacing());
return std::max(1, viewport()->height() / cellHeight);
}
void TerminalView::emitTerminalSize()
{
emit terminalSizeChanged(terminalColumns(), terminalRows());
}

66
src/terminal_view.h Normal file
View File

@@ -0,0 +1,66 @@
#ifndef ORBITHUB_TERMINAL_VIEW_H
#define ORBITHUB_TERMINAL_VIEW_H
#include <QTextEdit>
#include <array>
class QKeyEvent;
class QFocusEvent;
class QResizeEvent;
class TerminalView : public QTextEdit
{
Q_OBJECT
public:
explicit TerminalView(QWidget* parent = nullptr);
static QStringList themeNames();
void setThemeName(const QString& themeName);
void appendTerminalData(const QString& data);
signals:
void inputGenerated(const QString& input);
void terminalSizeChanged(int columns, int rows);
protected:
void keyPressEvent(QKeyEvent* event) override;
void focusInEvent(QFocusEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
private:
struct ThemePalette {
QString name;
QColor background;
QColor foreground;
std::array<QColor, 8> normal;
std::array<QColor, 8> bright;
};
ThemePalette m_palette;
QString m_pendingEscape;
QString m_rawHistory;
bool m_bold;
bool m_hasFgColor;
bool m_hasBgColor;
QColor m_fgColor;
QColor m_bgColor;
QTextCharFormat m_currentFormat;
static ThemePalette paletteByName(const QString& themeName);
static QColor colorFrom256Index(int index);
void applyThemePalette(const ThemePalette& palette);
void applyCurrentFormat();
void resetSgrState();
void handleSgrSequence(const QString& params);
void appendTextChunk(const QString& text);
QColor paletteColor(bool background, int index, bool bright) const;
void processData(const QString& data, bool storeInHistory);
int terminalColumns() const;
int terminalRows() const;
void emitTerminalSize();
};
#endif

View File

@@ -0,0 +1,39 @@
#include "unsupported_session_backend.h"
UnsupportedSessionBackend::UnsupportedSessionBackend(const Profile& profile, QObject* parent)
: SessionBackend(profile, parent)
{
}
void UnsupportedSessionBackend::connectSession(const SessionConnectOptions&)
{
const QString message = QStringLiteral("Protocol '%1' is not implemented yet.")
.arg(profile().protocol);
emit eventLogged(message);
emit stateChanged(SessionState::Failed, message);
emit connectionError(message, message);
}
void UnsupportedSessionBackend::disconnectSession()
{
emit stateChanged(SessionState::Disconnected,
QStringLiteral("No active connection for this protocol."));
}
void UnsupportedSessionBackend::reconnectSession(const SessionConnectOptions& options)
{
connectSession(options);
}
void UnsupportedSessionBackend::sendInput(const QString&)
{
emit eventLogged(QStringLiteral("Input ignored: protocol backend is not interactive."));
}
void UnsupportedSessionBackend::confirmHostKey(bool)
{
}
void UnsupportedSessionBackend::updateTerminalSize(int, int)
{
}

View File

@@ -0,0 +1,22 @@
#ifndef ORBITHUB_UNSUPPORTED_SESSION_BACKEND_H
#define ORBITHUB_UNSUPPORTED_SESSION_BACKEND_H
#include "session_backend.h"
class UnsupportedSessionBackend : public SessionBackend
{
Q_OBJECT
public:
explicit UnsupportedSessionBackend(const Profile& profile, QObject* parent = nullptr);
public slots:
void connectSession(const SessionConnectOptions& options) override;
void disconnectSession() override;
void reconnectSession(const SessionConnectOptions& options) override;
void sendInput(const QString& input) override;
void confirmHostKey(bool trustHost) override;
void updateTerminalSize(int columns, int rows) override;
};
#endif

61
third_party/KodoTerm/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,61 @@
# SPDX-License-Identifier: MIT
cmake_minimum_required(VERSION 3.21)
project(KodoTermVendor LANGUAGES C CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(LIBVTERM_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../libvterm")
file(GLOB VTERM_SOURCES CONFIGURE_DEPENDS
"${LIBVTERM_SOURCE_DIR}/src/*.c"
)
add_library(vterm STATIC ${VTERM_SOURCES})
target_include_directories(vterm PUBLIC
"${LIBVTERM_SOURCE_DIR}/include"
"${LIBVTERM_SOURCE_DIR}/src"
)
set(KODOTERM_SOURCES
src/KodoTerm.cpp
src/KodoTermConfig.cpp
src/PtyProcess.cpp
src/PtyProcess.h
include/KodoTerm/KodoTerm.hpp
include/KodoTerm/KodoTermConfig.hpp
KodoTermThemes.qrc
)
if(UNIX)
list(APPEND KODOTERM_SOURCES
src/PtyProcess_unix.cpp
src/PtyProcess_unix.h
)
elseif(WIN32)
list(APPEND KODOTERM_SOURCES
src/PtyProcess_win.cpp
src/PtyProcess_win.h
)
endif()
add_library(KodoTerm ${KODOTERM_SOURCES})
add_library(KodoTerm::KodoTerm ALIAS KodoTerm)
target_compile_features(KodoTerm PUBLIC cxx_std_20)
target_include_directories(KodoTerm PRIVATE src)
target_include_directories(KodoTerm PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_link_libraries(KodoTerm PUBLIC
Qt6::Core
Qt6::Gui
Qt6::Widgets
vterm
)

470
third_party/KodoTerm/KodoTermThemes.qrc vendored Normal file
View File

@@ -0,0 +1,470 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file>KodoTermThemes/konsole/BlackOnLightYellow.colorscheme</file>
<file>KodoTermThemes/konsole/BlackOnRandomLight.colorscheme</file>
<file>KodoTermThemes/konsole/BlackOnWhite.colorscheme</file>
<file>KodoTermThemes/konsole/BlueOnBlack.colorscheme</file>
<file>KodoTermThemes/konsole/Breeze.colorscheme</file>
<file>KodoTermThemes/konsole/Campbell.colorscheme</file>
<file>KodoTermThemes/konsole/DarkPastels.colorscheme</file>
<file>KodoTermThemes/konsole/GreenOnBlack.colorscheme</file>
<file>KodoTermThemes/konsole/Linux.colorscheme</file>
<file>KodoTermThemes/konsole/RedOnBlack.colorscheme</file>
<file>KodoTermThemes/konsole/Solarized.colorscheme</file>
<file>KodoTermThemes/konsole/SolarizedLight.colorscheme</file>
<file>KodoTermThemes/konsole/WhiteOnBlack.colorscheme</file>
<file>KodoTermThemes/windowsterminal/0x96f.json</file>
<file>KodoTermThemes/windowsterminal/12-bit Rainbow.json</file>
<file>KodoTermThemes/windowsterminal/3024 Day.json</file>
<file>KodoTermThemes/windowsterminal/3024 Night.json</file>
<file>KodoTermThemes/windowsterminal/Aardvark Blue.json</file>
<file>KodoTermThemes/windowsterminal/Abernathy.json</file>
<file>KodoTermThemes/windowsterminal/Adventure Time.json</file>
<file>KodoTermThemes/windowsterminal/Adventure.json</file>
<file>KodoTermThemes/windowsterminal/Adwaita Dark.json</file>
<file>KodoTermThemes/windowsterminal/Adwaita.json</file>
<file>KodoTermThemes/windowsterminal/Afterglow.json</file>
<file>KodoTermThemes/windowsterminal/Aizen Dark.json</file>
<file>KodoTermThemes/windowsterminal/Aizen Light.json</file>
<file>KodoTermThemes/windowsterminal/Alabaster.json</file>
<file>KodoTermThemes/windowsterminal/Alien Blood.json</file>
<file>KodoTermThemes/windowsterminal/Andromeda.json</file>
<file>KodoTermThemes/windowsterminal/Apple Classic.json</file>
<file>KodoTermThemes/windowsterminal/Apple System Colors Light.json</file>
<file>KodoTermThemes/windowsterminal/Apple System Colors.json</file>
<file>KodoTermThemes/windowsterminal/Arcoiris.json</file>
<file>KodoTermThemes/windowsterminal/Ardoise.json</file>
<file>KodoTermThemes/windowsterminal/Argonaut.json</file>
<file>KodoTermThemes/windowsterminal/Arthur.json</file>
<file>KodoTermThemes/windowsterminal/Atelier Sulphurpool.json</file>
<file>KodoTermThemes/windowsterminal/Atom One Dark.json</file>
<file>KodoTermThemes/windowsterminal/Atom One Light.json</file>
<file>KodoTermThemes/windowsterminal/Atom.json</file>
<file>KodoTermThemes/windowsterminal/Aura.json</file>
<file>KodoTermThemes/windowsterminal/Aurora.json</file>
<file>KodoTermThemes/windowsterminal/Ayu Light.json</file>
<file>KodoTermThemes/windowsterminal/Ayu Mirage.json</file>
<file>KodoTermThemes/windowsterminal/Ayu.json</file>
<file>KodoTermThemes/windowsterminal/Banana Blueberry.json</file>
<file>KodoTermThemes/windowsterminal/Batman.json</file>
<file>KodoTermThemes/windowsterminal/Belafonte Day.json</file>
<file>KodoTermThemes/windowsterminal/Belafonte Night.json</file>
<file>KodoTermThemes/windowsterminal/Birds Of Paradise.json</file>
<file>KodoTermThemes/windowsterminal/Black Metal (Bathory).json</file>
<file>KodoTermThemes/windowsterminal/Black Metal (Burzum).json</file>
<file>KodoTermThemes/windowsterminal/Black Metal (Dark Funeral).json</file>
<file>KodoTermThemes/windowsterminal/Black Metal (Gorgoroth).json</file>
<file>KodoTermThemes/windowsterminal/Black Metal (Immortal).json</file>
<file>KodoTermThemes/windowsterminal/Black Metal (Khold).json</file>
<file>KodoTermThemes/windowsterminal/Black Metal (Marduk).json</file>
<file>KodoTermThemes/windowsterminal/Black Metal (Mayhem).json</file>
<file>KodoTermThemes/windowsterminal/Black Metal (Nile).json</file>
<file>KodoTermThemes/windowsterminal/Black Metal (Venom).json</file>
<file>KodoTermThemes/windowsterminal/Black Metal.json</file>
<file>KodoTermThemes/windowsterminal/Blazer.json</file>
<file>KodoTermThemes/windowsterminal/Blue Berry Pie.json</file>
<file>KodoTermThemes/windowsterminal/Blue Dolphin.json</file>
<file>KodoTermThemes/windowsterminal/Blue Matrix.json</file>
<file>KodoTermThemes/windowsterminal/Bluloco Dark.json</file>
<file>KodoTermThemes/windowsterminal/Bluloco Light.json</file>
<file>KodoTermThemes/windowsterminal/Borland.json</file>
<file>KodoTermThemes/windowsterminal/Box.json</file>
<file>KodoTermThemes/windowsterminal/Breadog.json</file>
<file>KodoTermThemes/windowsterminal/Breeze.json</file>
<file>KodoTermThemes/windowsterminal/Bright Lights.json</file>
<file>KodoTermThemes/windowsterminal/Broadcast.json</file>
<file>KodoTermThemes/windowsterminal/Brogrammer.json</file>
<file>KodoTermThemes/windowsterminal/Builtin Dark.json</file>
<file>KodoTermThemes/windowsterminal/Builtin Light.json</file>
<file>KodoTermThemes/windowsterminal/Builtin Pastel Dark.json</file>
<file>KodoTermThemes/windowsterminal/Builtin Tango Dark.json</file>
<file>KodoTermThemes/windowsterminal/Builtin Tango Light.json</file>
<file>KodoTermThemes/windowsterminal/C64.json</file>
<file>KodoTermThemes/windowsterminal/CGA.json</file>
<file>KodoTermThemes/windowsterminal/CLRS.json</file>
<file>KodoTermThemes/windowsterminal/Calamity.json</file>
<file>KodoTermThemes/windowsterminal/Carbonfox.json</file>
<file>KodoTermThemes/windowsterminal/Catppuccin Frappe.json</file>
<file>KodoTermThemes/windowsterminal/Catppuccin Latte.json</file>
<file>KodoTermThemes/windowsterminal/Catppuccin Macchiato.json</file>
<file>KodoTermThemes/windowsterminal/Catppuccin Mocha.json</file>
<file>KodoTermThemes/windowsterminal/Chalk.json</file>
<file>KodoTermThemes/windowsterminal/Chalkboard.json</file>
<file>KodoTermThemes/windowsterminal/Challenger Deep.json</file>
<file>KodoTermThemes/windowsterminal/Chester.json</file>
<file>KodoTermThemes/windowsterminal/Ciapre.json</file>
<file>KodoTermThemes/windowsterminal/Citruszest.json</file>
<file>KodoTermThemes/windowsterminal/Cobalt Neon.json</file>
<file>KodoTermThemes/windowsterminal/Cobalt Next Dark.json</file>
<file>KodoTermThemes/windowsterminal/Cobalt Next Minimal.json</file>
<file>KodoTermThemes/windowsterminal/Cobalt Next.json</file>
<file>KodoTermThemes/windowsterminal/Cobalt2.json</file>
<file>KodoTermThemes/windowsterminal/Coffee Theme.json</file>
<file>KodoTermThemes/windowsterminal/Crayon Pony Fish.json</file>
<file>KodoTermThemes/windowsterminal/Cursor Dark.json</file>
<file>KodoTermThemes/windowsterminal/Cutie Pro.json</file>
<file>KodoTermThemes/windowsterminal/Cyberdyne.json</file>
<file>KodoTermThemes/windowsterminal/Cyberpunk Scarlet Protocol.json</file>
<file>KodoTermThemes/windowsterminal/Cyberpunk.json</file>
<file>KodoTermThemes/windowsterminal/Dark Modern.json</file>
<file>KodoTermThemes/windowsterminal/Dark Pastel.json</file>
<file>KodoTermThemes/windowsterminal/Dark+.json</file>
<file>KodoTermThemes/windowsterminal/Darkermatrix.json</file>
<file>KodoTermThemes/windowsterminal/Darkmatrix.json</file>
<file>KodoTermThemes/windowsterminal/Darkside.json</file>
<file>KodoTermThemes/windowsterminal/Dawnfox.json</file>
<file>KodoTermThemes/windowsterminal/Dayfox.json</file>
<file>KodoTermThemes/windowsterminal/Deep.json</file>
<file>KodoTermThemes/windowsterminal/Desert.json</file>
<file>KodoTermThemes/windowsterminal/Detuned.json</file>
<file>KodoTermThemes/windowsterminal/Dimidium.json</file>
<file>KodoTermThemes/windowsterminal/Dimmed Monokai.json</file>
<file>KodoTermThemes/windowsterminal/Django Reborn Again.json</file>
<file>KodoTermThemes/windowsterminal/Django Smooth.json</file>
<file>KodoTermThemes/windowsterminal/Django.json</file>
<file>KodoTermThemes/windowsterminal/Doom One.json</file>
<file>KodoTermThemes/windowsterminal/Doom Peacock.json</file>
<file>KodoTermThemes/windowsterminal/Dot Gov.json</file>
<file>KodoTermThemes/windowsterminal/Dracula+.json</file>
<file>KodoTermThemes/windowsterminal/Dracula.json</file>
<file>KodoTermThemes/windowsterminal/Duckbones.json</file>
<file>KodoTermThemes/windowsterminal/Duotone Dark.json</file>
<file>KodoTermThemes/windowsterminal/Duskfox.json</file>
<file>KodoTermThemes/windowsterminal/ENCOM.json</file>
<file>KodoTermThemes/windowsterminal/Earthsong.json</file>
<file>KodoTermThemes/windowsterminal/Electron Highlighter.json</file>
<file>KodoTermThemes/windowsterminal/Elegant.json</file>
<file>KodoTermThemes/windowsterminal/Elemental.json</file>
<file>KodoTermThemes/windowsterminal/Elementary.json</file>
<file>KodoTermThemes/windowsterminal/Embark.json</file>
<file>KodoTermThemes/windowsterminal/Embers Dark.json</file>
<file>KodoTermThemes/windowsterminal/Espresso Libre.json</file>
<file>KodoTermThemes/windowsterminal/Espresso.json</file>
<file>KodoTermThemes/windowsterminal/Everblush.json</file>
<file>KodoTermThemes/windowsterminal/Everforest Dark Hard.json</file>
<file>KodoTermThemes/windowsterminal/Everforest Light Med.json</file>
<file>KodoTermThemes/windowsterminal/Fahrenheit.json</file>
<file>KodoTermThemes/windowsterminal/Fairyfloss.json</file>
<file>KodoTermThemes/windowsterminal/Farmhouse Dark.json</file>
<file>KodoTermThemes/windowsterminal/Farmhouse Light.json</file>
<file>KodoTermThemes/windowsterminal/Fideloper.json</file>
<file>KodoTermThemes/windowsterminal/Firefly Traditional.json</file>
<file>KodoTermThemes/windowsterminal/Firefox Dev.json</file>
<file>KodoTermThemes/windowsterminal/Firewatch.json</file>
<file>KodoTermThemes/windowsterminal/Fish Tank.json</file>
<file>KodoTermThemes/windowsterminal/Flat.json</file>
<file>KodoTermThemes/windowsterminal/Flatland.json</file>
<file>KodoTermThemes/windowsterminal/Flexoki Dark.json</file>
<file>KodoTermThemes/windowsterminal/Flexoki Light.json</file>
<file>KodoTermThemes/windowsterminal/Floraverse.json</file>
<file>KodoTermThemes/windowsterminal/Forest Blue.json</file>
<file>KodoTermThemes/windowsterminal/Framer.json</file>
<file>KodoTermThemes/windowsterminal/Front End Delight.json</file>
<file>KodoTermThemes/windowsterminal/Fun Forrest.json</file>
<file>KodoTermThemes/windowsterminal/Galaxy.json</file>
<file>KodoTermThemes/windowsterminal/Galizur.json</file>
<file>KodoTermThemes/windowsterminal/Ghostty Default Style Dark.json</file>
<file>KodoTermThemes/windowsterminal/GitHub Dark Colorblind.json</file>
<file>KodoTermThemes/windowsterminal/GitHub Dark Default.json</file>
<file>KodoTermThemes/windowsterminal/GitHub Dark Dimmed.json</file>
<file>KodoTermThemes/windowsterminal/GitHub Dark High Contrast.json</file>
<file>KodoTermThemes/windowsterminal/GitHub Dark.json</file>
<file>KodoTermThemes/windowsterminal/GitHub Light Colorblind.json</file>
<file>KodoTermThemes/windowsterminal/GitHub Light Default.json</file>
<file>KodoTermThemes/windowsterminal/GitHub Light High Contrast.json</file>
<file>KodoTermThemes/windowsterminal/GitHub.json</file>
<file>KodoTermThemes/windowsterminal/GitLab Dark Grey.json</file>
<file>KodoTermThemes/windowsterminal/GitLab Dark.json</file>
<file>KodoTermThemes/windowsterminal/GitLab Light.json</file>
<file>KodoTermThemes/windowsterminal/Glacier.json</file>
<file>KodoTermThemes/windowsterminal/Grape.json</file>
<file>KodoTermThemes/windowsterminal/Grass.json</file>
<file>KodoTermThemes/windowsterminal/Grey Green.json</file>
<file>KodoTermThemes/windowsterminal/Gruber Darker.json</file>
<file>KodoTermThemes/windowsterminal/Gruvbox Dark Hard.json</file>
<file>KodoTermThemes/windowsterminal/Gruvbox Dark.json</file>
<file>KodoTermThemes/windowsterminal/Gruvbox Light Hard.json</file>
<file>KodoTermThemes/windowsterminal/Gruvbox Light.json</file>
<file>KodoTermThemes/windowsterminal/Gruvbox Material Dark.json</file>
<file>KodoTermThemes/windowsterminal/Gruvbox Material Light.json</file>
<file>KodoTermThemes/windowsterminal/Gruvbox Material.json</file>
<file>KodoTermThemes/windowsterminal/Guezwhoz.json</file>
<file>KodoTermThemes/windowsterminal/HaX0R Blue.json</file>
<file>KodoTermThemes/windowsterminal/HaX0R Gr33N.json</file>
<file>KodoTermThemes/windowsterminal/HaX0R R3D.json</file>
<file>KodoTermThemes/windowsterminal/Hacktober.json</file>
<file>KodoTermThemes/windowsterminal/Hardcore.json</file>
<file>KodoTermThemes/windowsterminal/Harper.json</file>
<file>KodoTermThemes/windowsterminal/Havn Daggry.json</file>
<file>KodoTermThemes/windowsterminal/Havn Skumring.json</file>
<file>KodoTermThemes/windowsterminal/Heeler.json</file>
<file>KodoTermThemes/windowsterminal/Highway.json</file>
<file>KodoTermThemes/windowsterminal/Hipster Green.json</file>
<file>KodoTermThemes/windowsterminal/Hivacruz.json</file>
<file>KodoTermThemes/windowsterminal/Homebrew.json</file>
<file>KodoTermThemes/windowsterminal/Hopscotch.256.json</file>
<file>KodoTermThemes/windowsterminal/Hopscotch.json</file>
<file>KodoTermThemes/windowsterminal/Horizon Bright.json</file>
<file>KodoTermThemes/windowsterminal/Horizon.json</file>
<file>KodoTermThemes/windowsterminal/Hot Dog Stand (Mustard).json</file>
<file>KodoTermThemes/windowsterminal/Hot Dog Stand.json</file>
<file>KodoTermThemes/windowsterminal/Hurtado.json</file>
<file>KodoTermThemes/windowsterminal/Hybrid.json</file>
<file>KodoTermThemes/windowsterminal/IBM 5153 CGA (Black).json</file>
<file>KodoTermThemes/windowsterminal/IBM 5153 CGA.json</file>
<file>KodoTermThemes/windowsterminal/IC Green PPL.json</file>
<file>KodoTermThemes/windowsterminal/IC Orange PPL.json</file>
<file>KodoTermThemes/windowsterminal/IR Black.json</file>
<file>KodoTermThemes/windowsterminal/IRIX Console.json</file>
<file>KodoTermThemes/windowsterminal/IRIX Terminal.json</file>
<file>KodoTermThemes/windowsterminal/Iceberg Dark.json</file>
<file>KodoTermThemes/windowsterminal/Iceberg Light.json</file>
<file>KodoTermThemes/windowsterminal/Idea.json</file>
<file>KodoTermThemes/windowsterminal/Idle Toes.json</file>
<file>KodoTermThemes/windowsterminal/Jackie Brown.json</file>
<file>KodoTermThemes/windowsterminal/Japanesque.json</file>
<file>KodoTermThemes/windowsterminal/Jellybeans.json</file>
<file>KodoTermThemes/windowsterminal/JetBrains Darcula.json</file>
<file>KodoTermThemes/windowsterminal/Jubi.json</file>
<file>KodoTermThemes/windowsterminal/Kanagawa Dragon.json</file>
<file>KodoTermThemes/windowsterminal/Kanagawa Wave.json</file>
<file>KodoTermThemes/windowsterminal/Kanagawabones.json</file>
<file>KodoTermThemes/windowsterminal/Kibble.json</file>
<file>KodoTermThemes/windowsterminal/Kitty Default.json</file>
<file>KodoTermThemes/windowsterminal/Kitty Low Contrast.json</file>
<file>KodoTermThemes/windowsterminal/Kolorit.json</file>
<file>KodoTermThemes/windowsterminal/Konsolas.json</file>
<file>KodoTermThemes/windowsterminal/Kurokula.json</file>
<file>KodoTermThemes/windowsterminal/Lab Fox.json</file>
<file>KodoTermThemes/windowsterminal/Laser.json</file>
<file>KodoTermThemes/windowsterminal/Later This Evening.json</file>
<file>KodoTermThemes/windowsterminal/Lavandula.json</file>
<file>KodoTermThemes/windowsterminal/Light Owl.json</file>
<file>KodoTermThemes/windowsterminal/Liquid Carbon Transparent.json</file>
<file>KodoTermThemes/windowsterminal/Liquid Carbon.json</file>
<file>KodoTermThemes/windowsterminal/Lovelace.json</file>
<file>KodoTermThemes/windowsterminal/Man Page.json</file>
<file>KodoTermThemes/windowsterminal/Mariana.json</file>
<file>KodoTermThemes/windowsterminal/Material Dark.json</file>
<file>KodoTermThemes/windowsterminal/Material Darker.json</file>
<file>KodoTermThemes/windowsterminal/Material Design Colors.json</file>
<file>KodoTermThemes/windowsterminal/Material Ocean.json</file>
<file>KodoTermThemes/windowsterminal/Material.json</file>
<file>KodoTermThemes/windowsterminal/Mathias.json</file>
<file>KodoTermThemes/windowsterminal/Matrix.json</file>
<file>KodoTermThemes/windowsterminal/Matte Black.json</file>
<file>KodoTermThemes/windowsterminal/Medallion.json</file>
<file>KodoTermThemes/windowsterminal/Melange Dark.json</file>
<file>KodoTermThemes/windowsterminal/Melange Light.json</file>
<file>KodoTermThemes/windowsterminal/Mellifluous.json</file>
<file>KodoTermThemes/windowsterminal/Mellow.json</file>
<file>KodoTermThemes/windowsterminal/Miasma.json</file>
<file>KodoTermThemes/windowsterminal/Midnight In Mojave.json</file>
<file>KodoTermThemes/windowsterminal/Mirage.json</file>
<file>KodoTermThemes/windowsterminal/Misterioso.json</file>
<file>KodoTermThemes/windowsterminal/Molokai.json</file>
<file>KodoTermThemes/windowsterminal/Mona Lisa.json</file>
<file>KodoTermThemes/windowsterminal/Monokai Classic.json</file>
<file>KodoTermThemes/windowsterminal/Monokai Pro Light Sun.json</file>
<file>KodoTermThemes/windowsterminal/Monokai Pro Light.json</file>
<file>KodoTermThemes/windowsterminal/Monokai Pro Machine.json</file>
<file>KodoTermThemes/windowsterminal/Monokai Pro Octagon.json</file>
<file>KodoTermThemes/windowsterminal/Monokai Pro Ristretto.json</file>
<file>KodoTermThemes/windowsterminal/Monokai Pro Spectrum.json</file>
<file>KodoTermThemes/windowsterminal/Monokai Pro.json</file>
<file>KodoTermThemes/windowsterminal/Monokai Remastered.json</file>
<file>KodoTermThemes/windowsterminal/Monokai Soda.json</file>
<file>KodoTermThemes/windowsterminal/Monokai Vivid.json</file>
<file>KodoTermThemes/windowsterminal/Moonfly.json</file>
<file>KodoTermThemes/windowsterminal/N0Tch2K.json</file>
<file>KodoTermThemes/windowsterminal/Neobones Dark.json</file>
<file>KodoTermThemes/windowsterminal/Neobones Light.json</file>
<file>KodoTermThemes/windowsterminal/Neon.json</file>
<file>KodoTermThemes/windowsterminal/Neopolitan.json</file>
<file>KodoTermThemes/windowsterminal/Neutron.json</file>
<file>KodoTermThemes/windowsterminal/Night Lion V1.json</file>
<file>KodoTermThemes/windowsterminal/Night Lion V2.json</file>
<file>KodoTermThemes/windowsterminal/Night Owl.json</file>
<file>KodoTermThemes/windowsterminal/Night Owlish Light.json</file>
<file>KodoTermThemes/windowsterminal/Nightfox.json</file>
<file>KodoTermThemes/windowsterminal/Niji.json</file>
<file>KodoTermThemes/windowsterminal/No Clown Fiesta Light.json</file>
<file>KodoTermThemes/windowsterminal/No Clown Fiesta.json</file>
<file>KodoTermThemes/windowsterminal/Nocturnal Winter.json</file>
<file>KodoTermThemes/windowsterminal/Nord Light.json</file>
<file>KodoTermThemes/windowsterminal/Nord Wave.json</file>
<file>KodoTermThemes/windowsterminal/Nord.json</file>
<file>KodoTermThemes/windowsterminal/Nordfox.json</file>
<file>KodoTermThemes/windowsterminal/Novel.json</file>
<file>KodoTermThemes/windowsterminal/Nvim Dark.json</file>
<file>KodoTermThemes/windowsterminal/Nvim Light.json</file>
<file>KodoTermThemes/windowsterminal/Obsidian.json</file>
<file>KodoTermThemes/windowsterminal/Ocean.json</file>
<file>KodoTermThemes/windowsterminal/Oceanic Material.json</file>
<file>KodoTermThemes/windowsterminal/Oceanic Next.json</file>
<file>KodoTermThemes/windowsterminal/Ollie.json</file>
<file>KodoTermThemes/windowsterminal/One Dark Two.json</file>
<file>KodoTermThemes/windowsterminal/One Double Dark.json</file>
<file>KodoTermThemes/windowsterminal/One Double Light.json</file>
<file>KodoTermThemes/windowsterminal/One Half Dark.json</file>
<file>KodoTermThemes/windowsterminal/One Half Light.json</file>
<file>KodoTermThemes/windowsterminal/Onenord Light.json</file>
<file>KodoTermThemes/windowsterminal/Onenord.json</file>
<file>KodoTermThemes/windowsterminal/Operator Mono Dark.json</file>
<file>KodoTermThemes/windowsterminal/Overnight Slumber.json</file>
<file>KodoTermThemes/windowsterminal/Oxocarbon.json</file>
<file>KodoTermThemes/windowsterminal/Pale Night Hc.json</file>
<file>KodoTermThemes/windowsterminal/Pandora.json</file>
<file>KodoTermThemes/windowsterminal/Paraiso Dark.json</file>
<file>KodoTermThemes/windowsterminal/Paul Millr.json</file>
<file>KodoTermThemes/windowsterminal/Pencil Dark.json</file>
<file>KodoTermThemes/windowsterminal/Pencil Light.json</file>
<file>KodoTermThemes/windowsterminal/Peppermint.json</file>
<file>KodoTermThemes/windowsterminal/Phala Green Dark.json</file>
<file>KodoTermThemes/windowsterminal/Piatto Light.json</file>
<file>KodoTermThemes/windowsterminal/Pnevma.json</file>
<file>KodoTermThemes/windowsterminal/Poimandres Darker.json</file>
<file>KodoTermThemes/windowsterminal/Poimandres Storm.json</file>
<file>KodoTermThemes/windowsterminal/Poimandres White.json</file>
<file>KodoTermThemes/windowsterminal/Poimandres.json</file>
<file>KodoTermThemes/windowsterminal/Popping And Locking.json</file>
<file>KodoTermThemes/windowsterminal/Powershell.json</file>
<file>KodoTermThemes/windowsterminal/Primary.json</file>
<file>KodoTermThemes/windowsterminal/Pro Light.json</file>
<file>KodoTermThemes/windowsterminal/Pro.json</file>
<file>KodoTermThemes/windowsterminal/Purple Rain.json</file>
<file>KodoTermThemes/windowsterminal/Purplepeter.json</file>
<file>KodoTermThemes/windowsterminal/Rapture.json</file>
<file>KodoTermThemes/windowsterminal/Raycast Dark.json</file>
<file>KodoTermThemes/windowsterminal/Raycast Light.json</file>
<file>KodoTermThemes/windowsterminal/Rebecca.json</file>
<file>KodoTermThemes/windowsterminal/Red Alert.json</file>
<file>KodoTermThemes/windowsterminal/Red Planet.json</file>
<file>KodoTermThemes/windowsterminal/Red Sands.json</file>
<file>KodoTermThemes/windowsterminal/Relaxed.json</file>
<file>KodoTermThemes/windowsterminal/Retro Legends.json</file>
<file>KodoTermThemes/windowsterminal/Retro.json</file>
<file>KodoTermThemes/windowsterminal/Rippedcasts.json</file>
<file>KodoTermThemes/windowsterminal/Rose Pine Dawn.json</file>
<file>KodoTermThemes/windowsterminal/Rose Pine Moon.json</file>
<file>KodoTermThemes/windowsterminal/Rose Pine.json</file>
<file>KodoTermThemes/windowsterminal/Rouge 2.json</file>
<file>KodoTermThemes/windowsterminal/Royal.json</file>
<file>KodoTermThemes/windowsterminal/Ryuuko.json</file>
<file>KodoTermThemes/windowsterminal/Sakura.json</file>
<file>KodoTermThemes/windowsterminal/Scarlet Protocol.json</file>
<file>KodoTermThemes/windowsterminal/Sea Shells.json</file>
<file>KodoTermThemes/windowsterminal/Seafoam Pastel.json</file>
<file>KodoTermThemes/windowsterminal/Selenized Black.json</file>
<file>KodoTermThemes/windowsterminal/Selenized Dark.json</file>
<file>KodoTermThemes/windowsterminal/Selenized Light.json</file>
<file>KodoTermThemes/windowsterminal/Seoulbones Dark.json</file>
<file>KodoTermThemes/windowsterminal/Seoulbones Light.json</file>
<file>KodoTermThemes/windowsterminal/Seti.json</file>
<file>KodoTermThemes/windowsterminal/Shades Of Purple.json</file>
<file>KodoTermThemes/windowsterminal/Shaman.json</file>
<file>KodoTermThemes/windowsterminal/Slate.json</file>
<file>KodoTermThemes/windowsterminal/Sleepy Hollow.json</file>
<file>KodoTermThemes/windowsterminal/Smyck.json</file>
<file>KodoTermThemes/windowsterminal/Snazzy Soft.json</file>
<file>KodoTermThemes/windowsterminal/Snazzy.json</file>
<file>KodoTermThemes/windowsterminal/Soft Server.json</file>
<file>KodoTermThemes/windowsterminal/Solarized Darcula.json</file>
<file>KodoTermThemes/windowsterminal/Solarized Dark Higher Contrast.json</file>
<file>KodoTermThemes/windowsterminal/Solarized Dark Patched.json</file>
<file>KodoTermThemes/windowsterminal/Solarized Osaka Night.json</file>
<file>KodoTermThemes/windowsterminal/Sonokai.json</file>
<file>KodoTermThemes/windowsterminal/Spacedust.json</file>
<file>KodoTermThemes/windowsterminal/Spacegray Bright.json</file>
<file>KodoTermThemes/windowsterminal/Spacegray Eighties Dull.json</file>
<file>KodoTermThemes/windowsterminal/Spacegray Eighties.json</file>
<file>KodoTermThemes/windowsterminal/Spacegray.json</file>
<file>KodoTermThemes/windowsterminal/Spiderman.json</file>
<file>KodoTermThemes/windowsterminal/Spring.json</file>
<file>KodoTermThemes/windowsterminal/Square.json</file>
<file>KodoTermThemes/windowsterminal/Squirrelsong Dark.json</file>
<file>KodoTermThemes/windowsterminal/Srcery.json</file>
<file>KodoTermThemes/windowsterminal/Starlight.json</file>
<file>KodoTermThemes/windowsterminal/Sublette.json</file>
<file>KodoTermThemes/windowsterminal/Subliminal.json</file>
<file>KodoTermThemes/windowsterminal/Sugarplum.json</file>
<file>KodoTermThemes/windowsterminal/Sundried.json</file>
<file>KodoTermThemes/windowsterminal/Symfonic.json</file>
<file>KodoTermThemes/windowsterminal/Synthwave Alpha.json</file>
<file>KodoTermThemes/windowsterminal/Synthwave Everything.json</file>
<file>KodoTermThemes/windowsterminal/Synthwave.json</file>
<file>KodoTermThemes/windowsterminal/Tango Adapted.json</file>
<file>KodoTermThemes/windowsterminal/Tango Half Adapted.json</file>
<file>KodoTermThemes/windowsterminal/Tearout.json</file>
<file>KodoTermThemes/windowsterminal/Teerb.json</file>
<file>KodoTermThemes/windowsterminal/Terafox.json</file>
<file>KodoTermThemes/windowsterminal/Terminal Basic Dark.json</file>
<file>KodoTermThemes/windowsterminal/Terminal Basic.json</file>
<file>KodoTermThemes/windowsterminal/Thayer Bright.json</file>
<file>KodoTermThemes/windowsterminal/The Hulk.json</file>
<file>KodoTermThemes/windowsterminal/Tinacious Design Dark.json</file>
<file>KodoTermThemes/windowsterminal/Tinacious Design Light.json</file>
<file>KodoTermThemes/windowsterminal/TokyoNight Day.json</file>
<file>KodoTermThemes/windowsterminal/TokyoNight Moon.json</file>
<file>KodoTermThemes/windowsterminal/TokyoNight Night.json</file>
<file>KodoTermThemes/windowsterminal/TokyoNight Storm.json</file>
<file>KodoTermThemes/windowsterminal/TokyoNight.json</file>
<file>KodoTermThemes/windowsterminal/Tomorrow Night Blue.json</file>
<file>KodoTermThemes/windowsterminal/Tomorrow Night Bright.json</file>
<file>KodoTermThemes/windowsterminal/Tomorrow Night Burns.json</file>
<file>KodoTermThemes/windowsterminal/Tomorrow Night Eighties.json</file>
<file>KodoTermThemes/windowsterminal/Tomorrow Night.json</file>
<file>KodoTermThemes/windowsterminal/Tomorrow.json</file>
<file>KodoTermThemes/windowsterminal/Toy Chest.json</file>
<file>KodoTermThemes/windowsterminal/Treehouse.json</file>
<file>KodoTermThemes/windowsterminal/Twilight.json</file>
<file>KodoTermThemes/windowsterminal/Ubuntu.json</file>
<file>KodoTermThemes/windowsterminal/Ultra Dark.json</file>
<file>KodoTermThemes/windowsterminal/Ultra Violent.json</file>
<file>KodoTermThemes/windowsterminal/Under The Sea.json</file>
<file>KodoTermThemes/windowsterminal/Unikitty.json</file>
<file>KodoTermThemes/windowsterminal/Urple.json</file>
<file>KodoTermThemes/windowsterminal/Vague.json</file>
<file>KodoTermThemes/windowsterminal/Vaughn.json</file>
<file>KodoTermThemes/windowsterminal/Vercel.json</file>
<file>KodoTermThemes/windowsterminal/Vesper.json</file>
<file>KodoTermThemes/windowsterminal/Vibrant Ink.json</file>
<file>KodoTermThemes/windowsterminal/Vimbones.json</file>
<file>KodoTermThemes/windowsterminal/Violet Dark.json</file>
<file>KodoTermThemes/windowsterminal/Violet Light.json</file>
<file>KodoTermThemes/windowsterminal/Violite.json</file>
<file>KodoTermThemes/windowsterminal/Warm Neon.json</file>
<file>KodoTermThemes/windowsterminal/Wez.json</file>
<file>KodoTermThemes/windowsterminal/Whimsy.json</file>
<file>KodoTermThemes/windowsterminal/Wild Cherry.json</file>
<file>KodoTermThemes/windowsterminal/Wilmersdorf.json</file>
<file>KodoTermThemes/windowsterminal/Wombat.json</file>
<file>KodoTermThemes/windowsterminal/Wryan.json</file>
<file>KodoTermThemes/windowsterminal/Xcode Dark hc.json</file>
<file>KodoTermThemes/windowsterminal/Xcode Dark.json</file>
<file>KodoTermThemes/windowsterminal/Xcode Light hc.json</file>
<file>KodoTermThemes/windowsterminal/Xcode Light.json</file>
<file>KodoTermThemes/windowsterminal/Xcode WWDC.json</file>
<file>KodoTermThemes/windowsterminal/Zenbones Dark.json</file>
<file>KodoTermThemes/windowsterminal/Zenbones Light.json</file>
<file>KodoTermThemes/windowsterminal/Zenbones.json</file>
<file>KodoTermThemes/windowsterminal/Zenburn.json</file>
<file>KodoTermThemes/windowsterminal/Zenburned.json</file>
<file>KodoTermThemes/windowsterminal/Zenwritten Dark.json</file>
<file>KodoTermThemes/windowsterminal/Zenwritten Light.json</file>
<file>KodoTermThemes/windowsterminal/branch.json</file>
<file>KodoTermThemes/windowsterminal/iTerm2 Dark Background.json</file>
<file>KodoTermThemes/windowsterminal/iTerm2 Default.json</file>
<file>KodoTermThemes/windowsterminal/iTerm2 Light Background.json</file>
<file>KodoTermThemes/windowsterminal/iTerm2 Pastel Dark Background.json</file>
<file>KodoTermThemes/windowsterminal/iTerm2 Smoooooth.json</file>
<file>KodoTermThemes/windowsterminal/iTerm2 Solarized Dark.json</file>
<file>KodoTermThemes/windowsterminal/iTerm2 Solarized Light.json</file>
<file>KodoTermThemes/windowsterminal/iTerm2 Tango Dark.json</file>
<file>KodoTermThemes/windowsterminal/iTerm2 Tango Light.json</file>
<file>KodoTermThemes/windowsterminal/novmbr.json</file>
<file>KodoTermThemes/windowsterminal/owl.json</file>
<file>KodoTermThemes/windowsterminal/traffic.json</file>
<file>KodoTermThemes/windowsterminal/urban.json</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,94 @@
[Background]
Color=255,255,221
[BackgroundIntense]
Color=255,255,221
[BackgroundFaint]
Color=255,255,221
[Color0]
Color=0,0,0
[Color0Intense]
Color=104,104,104
[Color0Faint]
Color=192,192,192
[Color1]
Color=178,24,24
[Color1Intense]
Color=255,84,84
[Color1Faint]
Color=224,142,142
[Color2]
Color=24,178,24
[Color2Intense]
Color=84,255,84
[Color2Faint]
Color=142,224,142
[Color3]
Color=178,104,24
[Color3Intense]
Color=255,255,84
[Color3Faint]
Color=224,224,142
[Color4]
Color=24,24,178
[Color4Intense]
Color=84,84,255
[Color4Faint]
Color=142,142,224
[Color5]
Color=178,24,178
[Color5Intense]
Color=255,84,255
[Color5Faint]
Color=224,142,224
[Color6]
Color=24,178,178
[Color6Intense]
Color=84,255,255
[Color6Faint]
Color=142,224,224
[Color7]
Color=178,178,178
[Color7Intense]
Color=255,255,255
[Color7Faint]
Color=142,142,142
[Foreground]
Color=0,0,0
[ForegroundIntense]
Bold=true
Color=0,0,0
[ForegroundFaint]
Color=0,0,0
[General]
Description=Black on Light Yellow
Opacity=1

View File

@@ -0,0 +1,104 @@
[Background]
Color=247,247,214
RandomHueRange=360
RandomSaturationRange=25
RandomLightnessRange=10
[BackgroundIntense]
Color=255,255,221
RandomHueRange=360
RandomSaturationRange=25
RandomLightnessRange=10
[BackgroundFaint]
Color=247,247,214
RandomHueRange=360
RandomSaturationRange=25
RandomLightnessRange=10
[Color0]
Color=0,0,0
[Color0Intense]
Color=104,104,104
[Color0Faint]
Color=192,192,192
[Color1]
Color=178,24,24
[Color1Intense]
Color=255,84,84
[Color1Faint]
Color=224,142,142
[Color2]
Color=24,178,24
[Color2Intense]
Color=84,255,84
[Color2Faint]
Color=142,224,142
[Color3]
Color=178,104,24
[Color3Intense]
Color=255,255,84
[Color3Faint]
Color=224,224,142
[Color4]
Color=24,24,178
[Color4Intense]
Color=84,84,255
[Color4Faint]
Color=142,142,224
[Color5]
Color=178,24,178
[Color5Intense]
Color=255,84,255
[Color5Faint]
Color=224,142,224
[Color6]
Color=24,178,178
[Color6Intense]
Color=84,255,255
[Color6Faint]
Color=142,224,224
[Color7]
Color=178,178,178
[Color7Intense]
Color=255,255,255
[Color7Faint]
Color=142,142,142
[Foreground]
Color=0,0,0
[ForegroundIntense]
Bold=true
Color=0,0,0
[ForegroundFaint]
Color=0,0,0
[General]
Description=Black on Random Light
ColorRandomization=true
Opacity=1

View File

@@ -0,0 +1,94 @@
[Background]
Color=255,255,255
[BackgroundIntense]
Color=255,255,255
[BackgroundFaint]
Color=255,255,255
[Color0]
Color=0,0,0
[Color0Intense]
Color=104,104,104
[Color0Faint]
Color=192,192,192
[Color1]
Color=178,24,24
[Color1Intense]
Color=255,84,84
[Color1Faint]
Color=224,142,142
[Color2]
Color=24,178,24
[Color2Intense]
Color=84,255,84
[Color2Faint]
Color=142,224,142
[Color3]
Color=178,104,24
[Color3Intense]
Color=255,255,84
[Color3Faint]
Color=224,224,142
[Color4]
Color=24,24,178
[Color4Intense]
Color=84,84,255
[Color4Faint]
Color=142,142,224
[Color5]
Color=178,24,178
[Color5Intense]
Color=255,84,255
[Color5Faint]
Color=224,142,224
[Color6]
Color=24,178,178
[Color6Intense]
Color=84,255,255
[Color6Faint]
Color=142,224,224
[Color7]
Color=178,178,178
[Color7Intense]
Color=255,255,255
[Color7Faint]
Color=142,142,142
[Foreground]
Color=0,0,0
[ForegroundIntense]
Bold=true
Color=0,0,0
[ForegroundFaint]
Color=0,0,0
[General]
Description=Black on White
Opacity=1

View File

@@ -0,0 +1,94 @@
[Background]
Color=0,0,0
[BackgroundIntense]
Color=0,0,0
[BackgroundFaint]
Color=0,0,0
[Color0]
Color=0,0,0
[Color0Intense]
Color=104,104,104
[Color0Faint]
Color=192,192,192
[Color1]
Color=250,0,0
[Color1Intense]
Color=75,93,255
[Color1Faint]
Color=250,0,0
[Color2]
Color=24,178,24
[Color2Intense]
Color=84,255,84
[Color2Faint]
Color=142,224,142
[Color3]
Color=178,104,24
[Color3Intense]
Color=255,255,84
[Color3Faint]
Color=224,224,142
[Color4]
Color=125,152,35
[Color4Intense]
Color=84,84,255
[Color4Faint]
Color=125,152,35
[Color5]
Color=225,30,225
[Color5Intense]
Color=255,84,255
[Color5Faint]
Color=175,29,175
[Color6]
Color=0,134,223
[Color6Intense]
Color=0,68,255
[Color6Faint]
Color=0,98,173
[Color7]
Color=255,255,255
[Color7Intense]
Color=50,50,50
[Color7Faint]
Color=200,200,200
[Foreground]
Color=0,119,255
[ForegroundIntense]
Bold=true
Color=23,74,240
[ForegroundFaint]
Color=0,90,195
[General]
Description=Blue on Black
Opacity=1

View File

@@ -0,0 +1,94 @@
[Background]
Color=35,38,39
[BackgroundFaint]
Color=49,54,59
[BackgroundIntense]
Color=0,0,0
[Color0]
Color=35,38,39
[Color0Faint]
Color=49,54,59
[Color0Intense]
Color=127,140,141
[Color1]
Color=237,21,21
[Color1Faint]
Color=120,50,40
[Color1Intense]
Color=192,57,43
[Color2]
Color=17,209,22
[Color2Faint]
Color=23,162,98
[Color2Intense]
Color=28,220,154
[Color3]
Color=246,116,0
[Color3Faint]
Color=182,86,25
[Color3Intense]
Color=253,188,75
[Color4]
Color=29,153,243
[Color4Faint]
Color=27,102,143
[Color4Intense]
Color=61,174,233
[Color5]
Color=155,89,182
[Color5Faint]
Color=97,74,115
[Color5Intense]
Color=142,68,173
[Color6]
Color=26,188,156
[Color6Faint]
Color=24,108,96
[Color6Intense]
Color=22,160,133
[Color7]
Color=252,252,252
[Color7Faint]
Color=99,104,109
[Color7Intense]
Color=255,255,255
[Foreground]
Color=252,252,252
[ForegroundFaint]
Color=239,240,241
[ForegroundIntense]
Color=61,174,233
[General]
Description=Breeze
Opacity=1
Wallpaper=

View File

@@ -0,0 +1,100 @@
[Background]
Color=12,12,12
[BackgroundFaint]
Color=44,44,44
[BackgroundIntense]
Color=0,0,0
[Color0]
Color=12,12,12
[Color0Faint]
Color=44,44,44
[Color0Intense]
Color=124,124,124
[Color1]
Color=197,15,31
[Color1Faint]
Color=150,11,25
[Color1Intense]
Color=231,72,86
[Color2]
Color=19,161,14
[Color2Faint]
Color=16,120,10
[Color2Intense]
Color=22,198,12
[Color3]
Color=193,156,0
[Color3Faint]
Color=150,120,0
[Color3Intense]
Color=249,241,165
[Color4]
Color=0,55,218
[Color4Faint]
Color=0,45,170
[Color4Intense]
Color=59,120,255
[Color5]
Color=136,23,152
[Color5Faint]
Color=106,18,120
[Color5Intense]
Color=180,0,158
[Color6]
Color=58,150,221
[Color6Faint]
Color=47,122,180
[Color6Intense]
Color=97,214,214
[Color7]
Color=204,204,204
[Color7Faint]
Color=170,170,170
[Color7Intense]
Color=242,242,242
[Foreground]
Color=204,204,204
[ForegroundFaint]
Color=170,170,170
[ForegroundIntense]
Color=255,255,255
[General]
Anchor=0.5,0.5
Blur=false
ColorRandomization=false
Description=Campbell
FillStyle=Tile
Opacity=1
Wallpaper=
WallpaperFlipType=NoFlip
WallpaperOpacity=1

View File

@@ -0,0 +1,103 @@
[Background]
Color=44,44,44
[BackgroundIntense]
Bold=true
Color=44,44,44
[BackgroundFaint]
Color=44,44,44
[Color0]
Color=63,63,63
[Color0Intense]
Bold=true
Color=112,144,128
[Color0Faint]
Color=52,52,52
[Color1]
Color=112,80,80
[Color1Intense]
Bold=true
Color=220,163,163
[Color1Faint]
Color=102,72,72
[Color2]
Color=96,180,138
[Color2Intense]
Bold=true
Color=114,213,163
[Color2Faint]
Color=87,163,124
[Color3]
Color=223,175,143
[Color3Intense]
Bold=true
Color=240,223,175
[Color3Faint]
Color=170,133,111
[Color4]
Color=154,184,215
[Color4Intense]
Bold=true
Color=148,191,243
[Color4Faint]
Color=117,141,161
[Color5]
Color=220,140,195
[Color5Intense]
Bold=true
Color=236,147,211
[Color5Faint]
Color=154,98,137
[Color6]
Color=140,208,211
[Color6Intense]
Bold=true
Color=147,224,227
[Color6Faint]
Color=107,159,161
[Color7]
Color=220,220,204
[Color7Intense]
Bold=true
Color=255,255,255
[Color7Faint]
Color=149,149,139
[Foreground]
Color=220,220,204
[ForegroundIntense]
Bold=true
Color=220,220,204
[ForegroundFaint]
Color=220,220,204
[General]
Description=Dark Pastels
Opacity=1

View File

@@ -0,0 +1,94 @@
[Background]
Color=0,0,0
[BackgroundIntense]
Color=0,0,0
[BackgroundFaint]
Color=0,0,0
[Color0]
Color=0,0,0
[Color0Intense]
Color=104,104,104
[Color0Faint]
Color=24,24,24
[Color1]
Color=250,75,75
[Color1Intense]
Color=255,84,84
[Color1Faint]
Color=101,25,25
[Color2]
Color=24,178,24
[Color2Intense]
Color=84,255,84
[Color2Faint]
Color=0,101,0
[Color3]
Color=178,104,24
[Color3Intense]
Color=255,255,84
[Color3Faint]
Color=101,74,0
[Color4]
Color=24,24,178
[Color4Intense]
Color=84,84,255
[Color4Faint]
Color=0,0,101
[Color5]
Color=225,30,225
[Color5Intense]
Color=255,84,255
[Color5Faint]
Color=95,5,95
[Color6]
Color=24,178,178
[Color6Intense]
Color=84,255,255
[Color6Faint]
Color=0,101,101
[Color7]
Color=178,178,178
[Color7Intense]
Color=255,255,255
[Color7Faint]
Color=101,101,101
[Foreground]
Color=24,240,24
[ForegroundIntense]
Bold=true
Color=24,240,24
[ForegroundFaint]
Color=18,200,18
[General]
Description=Green on Black
Opacity=1

View File

@@ -0,0 +1,92 @@
[Background]
Color=0,0,0
[BackgroundIntense]
Color=104,104,104
[BackgroundFaint]
Color=0,0,0
[Color0]
Color=0,0,0
[Color0Intense]
Color=104,104,104
[Color0Faint]
Color=24,24,24
[Color1]
Color=178,24,24
[Color1Intense]
Color=255,84,84
[Color1Faint]
Color=101,0,0
[Color2]
Color=24,178,24
[Color2Intense]
Color=84,255,84
[Color2Faint]
Color=0,101,0
[Color3]
Color=178,104,24
[Color3Intense]
Color=255,255,84
[Color3Faint]
Color=101,94,0
[Color4]
Color=24,24,178
[Color4Intense]
Color=84,84,255
[Color4Faint]
Color=0,0,101
[Color5]
Color=178,24,178
[Color5Intense]
Color=255,84,255
[Color5Faint]
Color=101,0,101
[Color6]
Color=24,178,178
[Color6Intense]
Color=84,255,255
[Color6Faint]
Color=0,101,101
[Color7]
Color=178,178,178
[Color7Intense]
Color=255,255,255
[Color7Faint]
Color=101,101,101
[Foreground]
Color=178,178,178
[ForegroundIntense]
Color=255,255,255
[ForegroundFaint]
Color=101,101,101
[General]
Description=Linux Colors

View File

@@ -0,0 +1,94 @@
[Background]
Color=0,0,0
[BackgroundIntense]
Color=0,0,0
[BackgroundFaint]
Color=0,0,0
[Color0]
Color=0,0,0
[Color0Intense]
Color=104,104,104
[Color0Faint]
Color=24,24,24
[Color1]
Color=250,142,8
[Color1Intense]
Color=255,84,84
[Color1Faint]
Color=101,25,0
[Color2]
Color=24,178,24
[Color2Intense]
Color=84,255,84
[Color2Faint]
Color=0,101,0
[Color3]
Color=178,104,24
[Color3Intense]
Color=255,255,84
[Color3Faint]
Color=101,74,0
[Color4]
Color=30,71,152
[Color4Intense]
Color=84,84,255
[Color4Faint]
Color=0,24,102
[Color5]
Color=225,30,225
[Color5Intense]
Color=255,84,255
[Color5Faint]
Color=95,5,95
[Color6]
Color=0,134,223
[Color6Intense]
Color=255,0,4
[Color6Faint]
Color=0,94,163
[Color7]
Color=255,255,255
[Color7Intense]
Color=50,50,50
[Color7Faint]
Color=101,101,101
[Foreground]
Color=255,0,0
[ForegroundIntense]
Bold=true
Color=24,240,24
[ForegroundFaint]
Color=205,0,0
[General]
Description=Red on Black
Opacity=1

View File

@@ -0,0 +1,93 @@
[Color0]
Color=7,54,66
[Color0Intense]
Color=0,43,54
[Color0Faint]
Color=6,48,59
[Color1]
Color=220,50,47
[Color1Intense]
Color=203,75,22
[Color1Faint]
Color=147,33,31
[Color2]
Color=133,153,0
[Color2Intense]
Color=88,110,117
[Color2Faint]
Color=94,106,0
[Color3]
Color=181,137,0
[Color3Intense]
Color=101,123,131
[Color3Faint]
Color=138,103,0
[Color4]
Color=38,139,210
[Color4Intense]
Color=131,148,150
[Color4Faint]
Color=20,77,115
[Color5]
Color=211,54,130
[Color5Intense]
Color=108,113,196
[Color5Faint]
Color=120,30,75
[Color6]
Color=42,161,152
[Color6Intense]
Color=147,161,161
[Color6Faint]
Color=24,94,88
[Color7]
Color=238,232,213
[Color7Intense]
Color=253,246,227
[Color7Faint]
Color=171,167,154
[Background]
Color=0,43,54
[BackgroundIntense]
Color=7,54,66
[BackgroundFaint]
Color=0,43,54
[Foreground]
Color=131,148,150
[ForegroundIntense]
Color=147,161,161
[ForegroundFaint]
Color=106,119,121
[General]
Description=Solarized
Opacity=1

View File

@@ -0,0 +1,93 @@
[Color0]
Color=7,54,66
[Color0Intense]
Color=0,43,54
[Color0Faint]
Color=8,65,80
[Color1]
Color=220,50,47
[Color1Intense]
Color=203,75,22
[Color1Faint]
Color=222,81,81
[Color2]
Color=133,153,0
[Color2Intense]
Color=88,110,117
[Color2Faint]
Color=153,168,39
[Color3]
Color=181,137,0
[Color3Intense]
Color=101,123,131
[Color3Faint]
Color=213,170,49
[Color4]
Color=38,139,210
[Color4Intense]
Color=131,148,150
[Color4Faint]
Color=80,173,226
[Color5]
Color=211,54,130
[Color5Intense]
Color=108,113,196
[Color5Faint]
Color=223,92,158
[Color6]
Color=42,161,152
[Color6Intense]
Color=147,161,161
[Color6Faint]
Color=78,211,200
[Color7]
Color=238,232,213
[Color7Intense]
Color=253,246,227
[Color7Faint]
Color=238,232,213
[Background]
Color=253,246,227
[BackgroundIntense]
Color=238,232,213
[BackgroundFaint]
Color=253,246,227
[Foreground]
Color=101,123,131
[ForegroundIntense]
Color=88,110,117
[ForegroundFaint]
Color=141,172,182
[General]
Description=Solarized Light
Opacity=1

View File

@@ -0,0 +1,94 @@
[Background]
Color=0,0,0
[BackgroundIntense]
Color=0,0,0
[BackgroundFaint]
Color=0,0,0
[Color0]
Color=0,0,0
[Color0Intense]
Color=104,104,104
[Color0Faint]
Color=24,24,24
[Color1]
Color=178,24,24
[Color1Intense]
Color=255,84,84
[Color1Faint]
Color=101,0,0
[Color2]
Color=24,178,24
[Color2Intense]
Color=84,255,84
[Color2Faint]
Color=0,101,0
[Color3]
Color=178,104,24
[Color3Intense]
Color=255,255,84
[Color3Faint]
Color=101,74,0
[Color4]
Color=24,24,178
[Color4Intense]
Color=84,84,255
[Color4Faint]
Color=0,0,101
[Color5]
Color=178,24,178
[Color5Intense]
Color=255,84,255
[Color5Faint]
Color=95,5,95
[Color6]
Color=24,178,178
[Color6Intense]
Color=84,255,255
[Color6Faint]
Color=24,178,178
[Color7]
Color=178,178,178
[Color7Intense]
Color=255,255,255
[Color7Faint]
Color=101,101,101
[Foreground]
Color=255,255,255
[ForegroundIntense]
Bold=true
Color=255,255,255
[ForegroundFaint]
Color=255,255,255
[General]
Description=White on Black
Opacity=1

View File

@@ -0,0 +1,23 @@
{
"name": "0x96f",
"black": "#262427",
"red": "#ff666d",
"green": "#b3e03a",
"yellow": "#ffc739",
"blue": "#00cde8",
"purple": "#a392e8",
"cyan": "#9deaf6",
"white": "#fcfcfa",
"brightBlack": "#545452",
"brightRed": "#ff7e83",
"brightGreen": "#bee55e",
"brightYellow": "#ffd05e",
"brightBlue": "#1bd5eb",
"brightPurple": "#b0a3eb",
"brightCyan": "#acedf8",
"brightWhite": "#fcfcfa",
"background": "#262427",
"foreground": "#fcfcfa",
"cursorColor": "#fcfcfa",
"selectionBackground": "#fcfcfa"
}

View File

@@ -0,0 +1,23 @@
{
"name": "12-bit Rainbow",
"black": "#000000",
"red": "#a03050",
"green": "#40d080",
"yellow": "#e09040",
"blue": "#3060b0",
"purple": "#603090",
"cyan": "#0090c0",
"white": "#dbded8",
"brightBlack": "#685656",
"brightRed": "#c06060",
"brightGreen": "#90d050",
"brightYellow": "#e0d000",
"brightBlue": "#00b0c0",
"brightPurple": "#801070",
"brightCyan": "#20b0c0",
"brightWhite": "#ffffff",
"background": "#040404",
"foreground": "#feffff",
"cursorColor": "#e0d000",
"selectionBackground": "#606060"
}

View File

@@ -0,0 +1,23 @@
{
"name": "3024 Day",
"black": "#090300",
"red": "#db2d20",
"green": "#01a252",
"yellow": "#caba00",
"blue": "#01a0e4",
"purple": "#a16a94",
"cyan": "#8fbece",
"white": "#a5a2a2",
"brightBlack": "#5c5855",
"brightRed": "#dbaec3",
"brightGreen": "#3a3432",
"brightYellow": "#4a4543",
"brightBlue": "#807d7c",
"brightPurple": "#bcbbba",
"brightCyan": "#cdab53",
"brightWhite": "#f7f7f7",
"background": "#f7f7f7",
"foreground": "#4a4543",
"cursorColor": "#4a4543",
"selectionBackground": "#a5a2a2"
}

View File

@@ -0,0 +1,23 @@
{
"name": "3024 Night",
"black": "#090300",
"red": "#db2d20",
"green": "#01a252",
"yellow": "#fded02",
"blue": "#01a0e4",
"purple": "#a16a94",
"cyan": "#b5e4f4",
"white": "#a5a2a2",
"brightBlack": "#5c5855",
"brightRed": "#e8bbd0",
"brightGreen": "#47413f",
"brightYellow": "#4a4543",
"brightBlue": "#807d7c",
"brightPurple": "#d6d5d4",
"brightCyan": "#cdab53",
"brightWhite": "#f7f7f7",
"background": "#090300",
"foreground": "#a5a2a2",
"cursorColor": "#a5a2a2",
"selectionBackground": "#4a4543"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Aardvark Blue",
"black": "#191919",
"red": "#aa342e",
"green": "#4b8c0f",
"yellow": "#dbba00",
"blue": "#1370d3",
"purple": "#c43ac3",
"cyan": "#008eb0",
"white": "#bebebe",
"brightBlack": "#525252",
"brightRed": "#f05b50",
"brightGreen": "#95dc55",
"brightYellow": "#ffe763",
"brightBlue": "#60a4ec",
"brightPurple": "#e26be2",
"brightCyan": "#60b6cb",
"brightWhite": "#f7f7f7",
"background": "#102040",
"foreground": "#dddddd",
"cursorColor": "#007acc",
"selectionBackground": "#bfdbfe"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Abernathy",
"black": "#000000",
"red": "#cd0000",
"green": "#00cd00",
"yellow": "#cdcd00",
"blue": "#1093f5",
"purple": "#cd00cd",
"cyan": "#00cdcd",
"white": "#faebd7",
"brightBlack": "#404040",
"brightRed": "#ff0000",
"brightGreen": "#00ff00",
"brightYellow": "#ffff00",
"brightBlue": "#11b5f6",
"brightPurple": "#ff00ff",
"brightCyan": "#00ffff",
"brightWhite": "#ffffff",
"background": "#111416",
"foreground": "#eeeeec",
"cursorColor": "#bbbbbb",
"selectionBackground": "#eeeeec"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Adventure Time",
"black": "#050404",
"red": "#bd0013",
"green": "#4ab118",
"yellow": "#e7741e",
"blue": "#0f4ac6",
"purple": "#665993",
"cyan": "#70a598",
"white": "#f8dcc0",
"brightBlack": "#4e7cbf",
"brightRed": "#fc5f5a",
"brightGreen": "#9eff6e",
"brightYellow": "#efc11a",
"brightBlue": "#1997c6",
"brightPurple": "#9b5953",
"brightCyan": "#c8faf4",
"brightWhite": "#f6f5fb",
"background": "#1f1d45",
"foreground": "#f8dcc0",
"cursorColor": "#efbf38",
"selectionBackground": "#706b4e"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Adventure",
"black": "#040404",
"red": "#d84a33",
"green": "#5da602",
"yellow": "#eebb6e",
"blue": "#417ab3",
"purple": "#e5c499",
"cyan": "#bdcfe5",
"white": "#dbded8",
"brightBlack": "#685656",
"brightRed": "#d76b42",
"brightGreen": "#99b52c",
"brightYellow": "#ffb670",
"brightBlue": "#97d7ef",
"brightPurple": "#aa7900",
"brightCyan": "#bdcfe5",
"brightWhite": "#e4d5c7",
"background": "#040404",
"foreground": "#feffff",
"cursorColor": "#feffff",
"selectionBackground": "#606060"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Adwaita Dark",
"black": "#241f31",
"red": "#c01c28",
"green": "#2ec27e",
"yellow": "#f5c211",
"blue": "#1e78e4",
"purple": "#9841bb",
"cyan": "#0ab9dc",
"white": "#c0bfbc",
"brightBlack": "#5e5c64",
"brightRed": "#ed333b",
"brightGreen": "#57e389",
"brightYellow": "#f8e45c",
"brightBlue": "#51a1ff",
"brightPurple": "#c061cb",
"brightCyan": "#4fd2fd",
"brightWhite": "#f6f5f4",
"background": "#1d1d20",
"foreground": "#ffffff",
"cursorColor": "#ffffff",
"selectionBackground": "#ffffff"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Adwaita",
"black": "#241f31",
"red": "#c01c28",
"green": "#2ec27e",
"yellow": "#e8b504",
"blue": "#1e78e4",
"purple": "#9841bb",
"cyan": "#0ab9dc",
"white": "#c0bfbc",
"brightBlack": "#5e5c64",
"brightRed": "#ed333b",
"brightGreen": "#4ad67c",
"brightYellow": "#d2be36",
"brightBlue": "#51a1ff",
"brightPurple": "#c061cb",
"brightCyan": "#4fd2fd",
"brightWhite": "#f6f5f4",
"background": "#ffffff",
"foreground": "#000000",
"cursorColor": "#000000",
"selectionBackground": "#c0bfbc"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Afterglow",
"black": "#151515",
"red": "#ac4142",
"green": "#7e8e50",
"yellow": "#e5b567",
"blue": "#6c99bb",
"purple": "#9f4e85",
"cyan": "#7dd6cf",
"white": "#d0d0d0",
"brightBlack": "#505050",
"brightRed": "#ac4142",
"brightGreen": "#7e8e50",
"brightYellow": "#e5b567",
"brightBlue": "#6c99bb",
"brightPurple": "#9f4e85",
"brightCyan": "#7dd6cf",
"brightWhite": "#f5f5f5",
"background": "#212121",
"foreground": "#d0d0d0",
"cursorColor": "#d0d0d0",
"selectionBackground": "#303030"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Aizen Dark",
"black": "#1a1a1a",
"red": "#f08898",
"green": "#a4e09c",
"yellow": "#f5dea4",
"blue": "#84b4f8",
"purple": "#c8a2f4",
"cyan": "#90dcd0",
"white": "#d0d6f0",
"brightBlack": "#444444",
"brightRed": "#f08898",
"brightGreen": "#a4e09c",
"brightYellow": "#f5dea4",
"brightBlue": "#84b4f8",
"brightPurple": "#c8a2f4",
"brightCyan": "#90dcd0",
"brightWhite": "#ffffff",
"background": "#1a1a1a",
"foreground": "#d0d6f0",
"cursorColor": "#f8b080",
"selectionBackground": "#333333"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Aizen Light",
"black": "#f0f2f6",
"red": "#d00c36",
"green": "#3e9e28",
"yellow": "#dd8c1a",
"blue": "#1c64f2",
"purple": "#8636ec",
"cyan": "#159096",
"white": "#4a4d66",
"brightBlack": "#adb2bc",
"brightRed": "#d00c36",
"brightGreen": "#3e9e28",
"brightYellow": "#dd8c1a",
"brightBlue": "#1c64f2",
"brightPurple": "#8636ec",
"brightCyan": "#159096",
"brightWhite": "#4a4d66",
"background": "#f0f2f6",
"foreground": "#4a4d66",
"cursorColor": "#fc6008",
"selectionBackground": "#bdc2cc"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Alabaster",
"black": "#000000",
"red": "#aa3731",
"green": "#448c27",
"yellow": "#cb9000",
"blue": "#325cc0",
"purple": "#7a3e9d",
"cyan": "#0083b2",
"white": "#b7b7b7",
"brightBlack": "#777777",
"brightRed": "#f05050",
"brightGreen": "#60cb00",
"brightYellow": "#f2af50",
"brightBlue": "#007acc",
"brightPurple": "#e64ce6",
"brightCyan": "#00aacb",
"brightWhite": "#f7f7f7",
"background": "#f7f7f7",
"foreground": "#000000",
"cursorColor": "#007acc",
"selectionBackground": "#bfdbfe"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Alien Blood",
"black": "#112616",
"red": "#7f2b27",
"green": "#2f7e25",
"yellow": "#717f24",
"blue": "#2f6a7f",
"purple": "#47587f",
"cyan": "#327f77",
"white": "#647d75",
"brightBlack": "#3c4812",
"brightRed": "#e08009",
"brightGreen": "#18e000",
"brightYellow": "#bde000",
"brightBlue": "#00aae0",
"brightPurple": "#0058e0",
"brightCyan": "#00e0c4",
"brightWhite": "#73fa91",
"background": "#0f1610",
"foreground": "#637d75",
"cursorColor": "#73fa91",
"selectionBackground": "#1d4125"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Andromeda",
"black": "#000000",
"red": "#cd3131",
"green": "#05bc79",
"yellow": "#e5e512",
"blue": "#2472c8",
"purple": "#bc3fbc",
"cyan": "#0fa8cd",
"white": "#e5e5e5",
"brightBlack": "#666666",
"brightRed": "#cd3131",
"brightGreen": "#05bc79",
"brightYellow": "#e5e512",
"brightBlue": "#2472c8",
"brightPurple": "#bc3fbc",
"brightCyan": "#0fa8cd",
"brightWhite": "#e5e5e5",
"background": "#262a33",
"foreground": "#e5e5e5",
"cursorColor": "#f8f8f0",
"selectionBackground": "#5a5c62"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Apple Classic",
"black": "#000000",
"red": "#c91b00",
"green": "#00c200",
"yellow": "#c7c400",
"blue": "#1c3fe1",
"purple": "#ca30c7",
"cyan": "#00c5c7",
"white": "#c7c7c7",
"brightBlack": "#686868",
"brightRed": "#ff6e67",
"brightGreen": "#5ffa68",
"brightYellow": "#fffc67",
"brightBlue": "#6871ff",
"brightPurple": "#ff77ff",
"brightCyan": "#60fdff",
"brightWhite": "#ffffff",
"background": "#2c2b2b",
"foreground": "#d5a200",
"cursorColor": "#c7c7c7",
"selectionBackground": "#6b5b02"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Apple System Colors Light",
"black": "#1a1a1a",
"red": "#cc372e",
"green": "#26a439",
"yellow": "#cdac08",
"blue": "#0869cb",
"purple": "#9647bf",
"cyan": "#479ec2",
"white": "#98989d",
"brightBlack": "#464646",
"brightRed": "#ff453a",
"brightGreen": "#32d74b",
"brightYellow": "#e5bc00",
"brightBlue": "#0a84ff",
"brightPurple": "#bf5af2",
"brightCyan": "#69c9f2",
"brightWhite": "#ffffff",
"background": "#feffff",
"foreground": "#000000",
"cursorColor": "#98989d",
"selectionBackground": "#abd8ff"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Apple System Colors",
"black": "#1a1a1a",
"red": "#cc372e",
"green": "#26a439",
"yellow": "#cdac08",
"blue": "#0869cb",
"purple": "#9647bf",
"cyan": "#479ec2",
"white": "#98989d",
"brightBlack": "#464646",
"brightRed": "#ff453a",
"brightGreen": "#32d74b",
"brightYellow": "#ffd60a",
"brightBlue": "#0a84ff",
"brightPurple": "#bf5af2",
"brightCyan": "#76d6ff",
"brightWhite": "#ffffff",
"background": "#1e1e1e",
"foreground": "#ffffff",
"cursorColor": "#98989d",
"selectionBackground": "#3f638b"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Arcoiris",
"black": "#333333",
"red": "#da2700",
"green": "#12c258",
"yellow": "#ffc656",
"blue": "#518bfc",
"purple": "#e37bd9",
"cyan": "#63fad5",
"white": "#bab2b2",
"brightBlack": "#777777",
"brightRed": "#ffb9b9",
"brightGreen": "#e3f6aa",
"brightYellow": "#ffddaa",
"brightBlue": "#b3e8f3",
"brightPurple": "#cbbaf9",
"brightCyan": "#bcffc7",
"brightWhite": "#efefef",
"background": "#201f1e",
"foreground": "#eee4d9",
"cursorColor": "#872929",
"selectionBackground": "#25524a"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Ardoise",
"black": "#2c2c2c",
"red": "#d3322d",
"green": "#588b35",
"yellow": "#fca93a",
"blue": "#2465c2",
"purple": "#7332b4",
"cyan": "#64e1b8",
"white": "#f7f7f7",
"brightBlack": "#535353",
"brightRed": "#fa5852",
"brightGreen": "#8dc252",
"brightYellow": "#ffea51",
"brightBlue": "#6ab5f8",
"brightPurple": "#be68ca",
"brightCyan": "#89ffdb",
"brightWhite": "#fefefe",
"background": "#1e1e1e",
"foreground": "#eaeaea",
"cursorColor": "#f7f7f7",
"selectionBackground": "#46515e"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Argonaut",
"black": "#232323",
"red": "#ff000f",
"green": "#8ce10b",
"yellow": "#ffb900",
"blue": "#008df8",
"purple": "#6d43a6",
"cyan": "#00d8eb",
"white": "#ffffff",
"brightBlack": "#444444",
"brightRed": "#ff2740",
"brightGreen": "#abe15b",
"brightYellow": "#ffd242",
"brightBlue": "#0092ff",
"brightPurple": "#9a5feb",
"brightCyan": "#67fff0",
"brightWhite": "#ffffff",
"background": "#0e1019",
"foreground": "#fffaf4",
"cursorColor": "#ff0018",
"selectionBackground": "#002a3b"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Arthur",
"black": "#3d352a",
"red": "#cd5c5c",
"green": "#86af80",
"yellow": "#e8ae5b",
"blue": "#6495ed",
"purple": "#deb887",
"cyan": "#b0c4de",
"white": "#bbaa99",
"brightBlack": "#554444",
"brightRed": "#cc5533",
"brightGreen": "#88aa22",
"brightYellow": "#ffa75d",
"brightBlue": "#87ceeb",
"brightPurple": "#996600",
"brightCyan": "#b0c4de",
"brightWhite": "#ddccbb",
"background": "#1c1c1c",
"foreground": "#ddeedd",
"cursorColor": "#e2bbef",
"selectionBackground": "#4d4d4d"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Atelier Sulphurpool",
"black": "#202746",
"red": "#c94922",
"green": "#ac9739",
"yellow": "#c08b30",
"blue": "#3d8fd1",
"purple": "#6679cc",
"cyan": "#22a2c9",
"white": "#979db4",
"brightBlack": "#6b7394",
"brightRed": "#c76b29",
"brightGreen": "#4f587c",
"brightYellow": "#5e6687",
"brightBlue": "#898ea4",
"brightPurple": "#dfe2f1",
"brightCyan": "#9c637a",
"brightWhite": "#f5f7ff",
"background": "#202746",
"foreground": "#979db4",
"cursorColor": "#979db4",
"selectionBackground": "#5e6687"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Atom One Dark",
"black": "#21252b",
"red": "#e06c75",
"green": "#98c379",
"yellow": "#e5c07b",
"blue": "#61afef",
"purple": "#c678dd",
"cyan": "#56b6c2",
"white": "#abb2bf",
"brightBlack": "#767676",
"brightRed": "#e06c75",
"brightGreen": "#98c379",
"brightYellow": "#e5c07b",
"brightBlue": "#61afef",
"brightPurple": "#c678dd",
"brightCyan": "#56b6c2",
"brightWhite": "#abb2bf",
"background": "#21252b",
"foreground": "#abb2bf",
"cursorColor": "#abb2bf",
"selectionBackground": "#323844"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Atom One Light",
"black": "#000000",
"red": "#de3e35",
"green": "#3f953a",
"yellow": "#d2b67c",
"blue": "#2f5af3",
"purple": "#950095",
"cyan": "#3f953a",
"white": "#bbbbbb",
"brightBlack": "#000000",
"brightRed": "#de3e35",
"brightGreen": "#3f953a",
"brightYellow": "#d2b67c",
"brightBlue": "#2f5af3",
"brightPurple": "#a00095",
"brightCyan": "#3f953a",
"brightWhite": "#ffffff",
"background": "#f9f9f9",
"foreground": "#2a2c33",
"cursorColor": "#bbbbbb",
"selectionBackground": "#ededed"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Atom",
"black": "#000000",
"red": "#fd5ff1",
"green": "#87c38a",
"yellow": "#ffd7b1",
"blue": "#85befd",
"purple": "#b9b6fc",
"cyan": "#85befd",
"white": "#e0e0e0",
"brightBlack": "#4c4c4c",
"brightRed": "#fd5ff1",
"brightGreen": "#94fa36",
"brightYellow": "#f5ffa8",
"brightBlue": "#96cbfe",
"brightPurple": "#b9b6fc",
"brightCyan": "#85befd",
"brightWhite": "#e0e0e0",
"background": "#161719",
"foreground": "#c5c8c6",
"cursorColor": "#d0d0d0",
"selectionBackground": "#444444"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Aura",
"black": "#110f18",
"red": "#ff6767",
"green": "#61ffca",
"yellow": "#ffca85",
"blue": "#a277ff",
"purple": "#a277ff",
"cyan": "#61ffca",
"white": "#edecee",
"brightBlack": "#4d4d4d",
"brightRed": "#ffca85",
"brightGreen": "#a277ff",
"brightYellow": "#ffca85",
"brightBlue": "#a277ff",
"brightPurple": "#a277ff",
"brightCyan": "#61ffca",
"brightWhite": "#edecee",
"background": "#15141b",
"foreground": "#edecee",
"cursorColor": "#a277ff",
"selectionBackground": "#a277ff"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Aurora",
"black": "#23262e",
"red": "#f0266f",
"green": "#8fd46d",
"yellow": "#ffe66d",
"blue": "#102ee4",
"purple": "#ee5d43",
"cyan": "#03d6b8",
"white": "#c74ded",
"brightBlack": "#4f545e",
"brightRed": "#f92672",
"brightGreen": "#8fd46d",
"brightYellow": "#ffe66d",
"brightBlue": "#03d6b8",
"brightPurple": "#ee5d43",
"brightCyan": "#03d6b8",
"brightWhite": "#c74ded",
"background": "#23262e",
"foreground": "#ffca28",
"cursorColor": "#ee5d43",
"selectionBackground": "#292e38"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Ayu Light",
"black": "#000000",
"red": "#ea6c6d",
"green": "#6cbf43",
"yellow": "#eca944",
"blue": "#3199e1",
"purple": "#9e75c7",
"cyan": "#46ba94",
"white": "#bababa",
"brightBlack": "#686868",
"brightRed": "#f07171",
"brightGreen": "#86b300",
"brightYellow": "#f2ae49",
"brightBlue": "#399ee6",
"brightPurple": "#a37acc",
"brightCyan": "#4cbf99",
"brightWhite": "#d1d1d1",
"background": "#f8f9fa",
"foreground": "#5c6166",
"cursorColor": "#ffaa33",
"selectionBackground": "#035bd6"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Ayu Mirage",
"black": "#171b24",
"red": "#ed8274",
"green": "#87d96c",
"yellow": "#facc6e",
"blue": "#6dcbfa",
"purple": "#dabafa",
"cyan": "#90e1c6",
"white": "#c7c7c7",
"brightBlack": "#686868",
"brightRed": "#f28779",
"brightGreen": "#d5ff80",
"brightYellow": "#ffd173",
"brightBlue": "#73d0ff",
"brightPurple": "#dfbfff",
"brightCyan": "#95e6cb",
"brightWhite": "#ffffff",
"background": "#1f2430",
"foreground": "#cccac2",
"cursorColor": "#ffcc66",
"selectionBackground": "#409fff"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Ayu",
"black": "#11151c",
"red": "#ea6c73",
"green": "#7fd962",
"yellow": "#f9af4f",
"blue": "#53bdfa",
"purple": "#cda1fa",
"cyan": "#90e1c6",
"white": "#c7c7c7",
"brightBlack": "#686868",
"brightRed": "#f07178",
"brightGreen": "#aad94c",
"brightYellow": "#ffb454",
"brightBlue": "#59c2ff",
"brightPurple": "#d2a6ff",
"brightCyan": "#95e6cb",
"brightWhite": "#ffffff",
"background": "#0b0e14",
"foreground": "#bfbdb6",
"cursorColor": "#e6b450",
"selectionBackground": "#409fff"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Banana Blueberry",
"black": "#17141f",
"red": "#ff6b7f",
"green": "#00bd9c",
"yellow": "#e6c62f",
"blue": "#22e8df",
"purple": "#dc396a",
"cyan": "#56b6c2",
"white": "#f1f1f1",
"brightBlack": "#495162",
"brightRed": "#fe9ea1",
"brightGreen": "#98c379",
"brightYellow": "#f9e46b",
"brightBlue": "#91fff4",
"brightPurple": "#da70d6",
"brightCyan": "#bcf3ff",
"brightWhite": "#ffffff",
"background": "#191323",
"foreground": "#cccccc",
"cursorColor": "#e07d13",
"selectionBackground": "#220525"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Batman",
"black": "#1b1d1e",
"red": "#e6dc44",
"green": "#c8be46",
"yellow": "#f4fd22",
"blue": "#737174",
"purple": "#747271",
"cyan": "#62605f",
"white": "#c6c5bf",
"brightBlack": "#505354",
"brightRed": "#fff78e",
"brightGreen": "#fff27d",
"brightYellow": "#feed6c",
"brightBlue": "#919495",
"brightPurple": "#9a9a9d",
"brightCyan": "#a3a3a6",
"brightWhite": "#dadbd6",
"background": "#1b1d1e",
"foreground": "#6f6f6f",
"cursorColor": "#fcef0c",
"selectionBackground": "#4d504c"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Belafonte Day",
"black": "#20111b",
"red": "#be100e",
"green": "#858162",
"yellow": "#d08b30",
"blue": "#426a79",
"purple": "#97522c",
"cyan": "#989a9c",
"white": "#968c83",
"brightBlack": "#5e5252",
"brightRed": "#be100e",
"brightGreen": "#858162",
"brightYellow": "#d08b30",
"brightBlue": "#426a79",
"brightPurple": "#97522c",
"brightCyan": "#989a9c",
"brightWhite": "#d5ccba",
"background": "#d5ccba",
"foreground": "#45373c",
"cursorColor": "#45373c",
"selectionBackground": "#968c83"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Belafonte Night",
"black": "#20111b",
"red": "#be100e",
"green": "#858162",
"yellow": "#eaa549",
"blue": "#426a79",
"purple": "#97522c",
"cyan": "#989a9c",
"white": "#968c83",
"brightBlack": "#5e5252",
"brightRed": "#be100e",
"brightGreen": "#858162",
"brightYellow": "#eaa549",
"brightBlue": "#426a79",
"brightPurple": "#97522c",
"brightCyan": "#989a9c",
"brightWhite": "#d5ccba",
"background": "#20111b",
"foreground": "#968c83",
"cursorColor": "#968c83",
"selectionBackground": "#45373c"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Birds Of Paradise",
"black": "#573d26",
"red": "#be2d26",
"green": "#6ba18a",
"yellow": "#e99d2a",
"blue": "#5a86ad",
"purple": "#ac80a6",
"cyan": "#74a6ad",
"white": "#e0dbb7",
"brightBlack": "#9b6c4a",
"brightRed": "#e84627",
"brightGreen": "#95d8ba",
"brightYellow": "#d0d150",
"brightBlue": "#b8d3ed",
"brightPurple": "#d19ecb",
"brightCyan": "#93cfd7",
"brightWhite": "#fff9d5",
"background": "#2a1f1d",
"foreground": "#e0dbb7",
"cursorColor": "#644a33",
"selectionBackground": "#563c27"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Black Metal (Bathory)",
"black": "#000000",
"red": "#5f8787",
"green": "#fbcb97",
"yellow": "#e78a53",
"blue": "#888888",
"purple": "#999999",
"cyan": "#aaaaaa",
"white": "#c1c1c1",
"brightBlack": "#404040",
"brightRed": "#5f8787",
"brightGreen": "#fbcb97",
"brightYellow": "#e78a53",
"brightBlue": "#888888",
"brightPurple": "#999999",
"brightCyan": "#aaaaaa",
"brightWhite": "#c1c1c1",
"background": "#000000",
"foreground": "#c1c1c1",
"cursorColor": "#c1c1c1",
"selectionBackground": "#c1c1c1"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Black Metal (Burzum)",
"black": "#000000",
"red": "#5f8787",
"green": "#ddeecc",
"yellow": "#99bbaa",
"blue": "#888888",
"purple": "#999999",
"cyan": "#aaaaaa",
"white": "#c1c1c1",
"brightBlack": "#404040",
"brightRed": "#5f8787",
"brightGreen": "#ddeecc",
"brightYellow": "#99bbaa",
"brightBlue": "#888888",
"brightPurple": "#999999",
"brightCyan": "#aaaaaa",
"brightWhite": "#c1c1c1",
"background": "#000000",
"foreground": "#c1c1c1",
"cursorColor": "#c1c1c1",
"selectionBackground": "#c1c1c1"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Black Metal (Dark Funeral)",
"black": "#000000",
"red": "#5f8787",
"green": "#d0dfee",
"yellow": "#5f81a5",
"blue": "#888888",
"purple": "#999999",
"cyan": "#aaaaaa",
"white": "#c1c1c1",
"brightBlack": "#404040",
"brightRed": "#5f8787",
"brightGreen": "#d0dfee",
"brightYellow": "#5f81a5",
"brightBlue": "#888888",
"brightPurple": "#999999",
"brightCyan": "#aaaaaa",
"brightWhite": "#c1c1c1",
"background": "#000000",
"foreground": "#c1c1c1",
"cursorColor": "#c1c1c1",
"selectionBackground": "#c1c1c1"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Black Metal (Gorgoroth)",
"black": "#000000",
"red": "#5f8787",
"green": "#9b8d7f",
"yellow": "#8c7f70",
"blue": "#888888",
"purple": "#999999",
"cyan": "#aaaaaa",
"white": "#c1c1c1",
"brightBlack": "#404040",
"brightRed": "#5f8787",
"brightGreen": "#9b8d7f",
"brightYellow": "#8c7f70",
"brightBlue": "#888888",
"brightPurple": "#999999",
"brightCyan": "#aaaaaa",
"brightWhite": "#c1c1c1",
"background": "#000000",
"foreground": "#c1c1c1",
"cursorColor": "#c1c1c1",
"selectionBackground": "#c1c1c1"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Black Metal (Immortal)",
"black": "#000000",
"red": "#5f8787",
"green": "#7799bb",
"yellow": "#556677",
"blue": "#888888",
"purple": "#999999",
"cyan": "#aaaaaa",
"white": "#c1c1c1",
"brightBlack": "#404040",
"brightRed": "#5f8787",
"brightGreen": "#7799bb",
"brightYellow": "#556677",
"brightBlue": "#888888",
"brightPurple": "#999999",
"brightCyan": "#aaaaaa",
"brightWhite": "#c1c1c1",
"background": "#000000",
"foreground": "#c1c1c1",
"cursorColor": "#c1c1c1",
"selectionBackground": "#c1c1c1"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Black Metal (Khold)",
"black": "#000000",
"red": "#5f8787",
"green": "#eceee3",
"yellow": "#974b46",
"blue": "#888888",
"purple": "#999999",
"cyan": "#aaaaaa",
"white": "#c1c1c1",
"brightBlack": "#404040",
"brightRed": "#5f8787",
"brightGreen": "#eceee3",
"brightYellow": "#974b46",
"brightBlue": "#888888",
"brightPurple": "#999999",
"brightCyan": "#aaaaaa",
"brightWhite": "#c1c1c1",
"background": "#000000",
"foreground": "#c1c1c1",
"cursorColor": "#c1c1c1",
"selectionBackground": "#c1c1c1"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Black Metal (Marduk)",
"black": "#000000",
"red": "#5f8787",
"green": "#a5aaa7",
"yellow": "#626b67",
"blue": "#888888",
"purple": "#999999",
"cyan": "#aaaaaa",
"white": "#c1c1c1",
"brightBlack": "#404040",
"brightRed": "#5f8787",
"brightGreen": "#a5aaa7",
"brightYellow": "#626b67",
"brightBlue": "#888888",
"brightPurple": "#999999",
"brightCyan": "#aaaaaa",
"brightWhite": "#c1c1c1",
"background": "#000000",
"foreground": "#c1c1c1",
"cursorColor": "#c1c1c1",
"selectionBackground": "#c1c1c1"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Black Metal (Mayhem)",
"black": "#000000",
"red": "#5f8787",
"green": "#f3ecd4",
"yellow": "#eecc6c",
"blue": "#888888",
"purple": "#999999",
"cyan": "#aaaaaa",
"white": "#c1c1c1",
"brightBlack": "#404040",
"brightRed": "#5f8787",
"brightGreen": "#f3ecd4",
"brightYellow": "#eecc6c",
"brightBlue": "#888888",
"brightPurple": "#999999",
"brightCyan": "#aaaaaa",
"brightWhite": "#c1c1c1",
"background": "#000000",
"foreground": "#c1c1c1",
"cursorColor": "#c1c1c1",
"selectionBackground": "#c1c1c1"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Black Metal (Nile)",
"black": "#000000",
"red": "#5f8787",
"green": "#aa9988",
"yellow": "#777755",
"blue": "#888888",
"purple": "#999999",
"cyan": "#aaaaaa",
"white": "#c1c1c1",
"brightBlack": "#404040",
"brightRed": "#5f8787",
"brightGreen": "#aa9988",
"brightYellow": "#777755",
"brightBlue": "#888888",
"brightPurple": "#999999",
"brightCyan": "#aaaaaa",
"brightWhite": "#c1c1c1",
"background": "#000000",
"foreground": "#c1c1c1",
"cursorColor": "#c1c1c1",
"selectionBackground": "#c1c1c1"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Black Metal (Venom)",
"black": "#000000",
"red": "#5f8787",
"green": "#f8f7f2",
"yellow": "#79241f",
"blue": "#888888",
"purple": "#999999",
"cyan": "#aaaaaa",
"white": "#c1c1c1",
"brightBlack": "#404040",
"brightRed": "#5f8787",
"brightGreen": "#f8f7f2",
"brightYellow": "#79241f",
"brightBlue": "#888888",
"brightPurple": "#999999",
"brightCyan": "#aaaaaa",
"brightWhite": "#c1c1c1",
"background": "#000000",
"foreground": "#c1c1c1",
"cursorColor": "#c1c1c1",
"selectionBackground": "#c1c1c1"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Black Metal",
"black": "#000000",
"red": "#486e6f",
"green": "#dd9999",
"yellow": "#a06666",
"blue": "#888888",
"purple": "#999999",
"cyan": "#aaaaaa",
"white": "#c1c1c1",
"brightBlack": "#404040",
"brightRed": "#486e6f",
"brightGreen": "#dd9999",
"brightYellow": "#a06666",
"brightBlue": "#888888",
"brightPurple": "#999999",
"brightCyan": "#aaaaaa",
"brightWhite": "#c1c1c1",
"background": "#000000",
"foreground": "#c1c1c1",
"cursorColor": "#c1c1c1",
"selectionBackground": "#c1c1c1"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Blazer",
"black": "#000000",
"red": "#b87a7a",
"green": "#7ab87a",
"yellow": "#b8b87a",
"blue": "#7a7ab8",
"purple": "#b87ab8",
"cyan": "#7ab8b8",
"white": "#d9d9d9",
"brightBlack": "#4c4c4c",
"brightRed": "#dbbdbd",
"brightGreen": "#bddbbd",
"brightYellow": "#dbdbbd",
"brightBlue": "#bdbddb",
"brightPurple": "#dbbddb",
"brightCyan": "#bddbdb",
"brightWhite": "#ffffff",
"background": "#0d1926",
"foreground": "#d9e6f2",
"cursorColor": "#d9e6f2",
"selectionBackground": "#c1ddff"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Blue Berry Pie",
"black": "#0a4c62",
"red": "#99246e",
"green": "#5cb1b3",
"yellow": "#eab9a8",
"blue": "#90a5bd",
"purple": "#9d54a7",
"cyan": "#7e83cc",
"white": "#f0e8d6",
"brightBlack": "#463c5d",
"brightRed": "#c87272",
"brightGreen": "#0a6c7e",
"brightYellow": "#7a3188",
"brightBlue": "#5f3d63",
"brightPurple": "#bc94b7",
"brightCyan": "#5e6071",
"brightWhite": "#0a6c7e",
"background": "#1c0c28",
"foreground": "#babab9",
"cursorColor": "#fcfad6",
"selectionBackground": "#606060"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Blue Dolphin",
"black": "#292d3e",
"red": "#ff8288",
"green": "#b4e88d",
"yellow": "#f4d69f",
"blue": "#82aaff",
"purple": "#e9c1ff",
"cyan": "#89ebff",
"white": "#d0d0d0",
"brightBlack": "#9094a4",
"brightRed": "#ff8b92",
"brightGreen": "#ddffa7",
"brightYellow": "#ffe585",
"brightBlue": "#9cc4ff",
"brightPurple": "#ddb0f6",
"brightCyan": "#a3f7ff",
"brightWhite": "#ffffff",
"background": "#006984",
"foreground": "#c5f2ff",
"cursorColor": "#ffcc00",
"selectionBackground": "#2baeca"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Blue Matrix",
"black": "#101116",
"red": "#ff5680",
"green": "#00ff9c",
"yellow": "#fffc58",
"blue": "#00b0ff",
"purple": "#d57bff",
"cyan": "#76c1ff",
"white": "#c7c7c7",
"brightBlack": "#686868",
"brightRed": "#ff6e67",
"brightGreen": "#5ffa68",
"brightYellow": "#fffc67",
"brightBlue": "#6871ff",
"brightPurple": "#d682ec",
"brightCyan": "#60fdff",
"brightWhite": "#ffffff",
"background": "#101116",
"foreground": "#00a2ff",
"cursorColor": "#76ff9f",
"selectionBackground": "#c1deff"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Bluloco Dark",
"black": "#41444d",
"red": "#fc2f52",
"green": "#25a45c",
"yellow": "#ff936a",
"blue": "#3476ff",
"purple": "#7a82da",
"cyan": "#4483aa",
"white": "#cdd4e0",
"brightBlack": "#8f9aae",
"brightRed": "#ff6480",
"brightGreen": "#3fc56b",
"brightYellow": "#f9c859",
"brightBlue": "#10b1fe",
"brightPurple": "#ff78f8",
"brightCyan": "#5fb9bc",
"brightWhite": "#ffffff",
"background": "#282c34",
"foreground": "#b9c0cb",
"cursorColor": "#ffcc00",
"selectionBackground": "#b9c0ca"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Bluloco Light",
"black": "#373a41",
"red": "#d52753",
"green": "#23974a",
"yellow": "#df631c",
"blue": "#275fe4",
"purple": "#823ff1",
"cyan": "#27618d",
"white": "#babbc2",
"brightBlack": "#676a77",
"brightRed": "#ff6480",
"brightGreen": "#3cbc66",
"brightYellow": "#c5a332",
"brightBlue": "#0099e1",
"brightPurple": "#ce33c0",
"brightCyan": "#6d93bb",
"brightWhite": "#d3d3d3",
"background": "#f9f9f9",
"foreground": "#373a41",
"cursorColor": "#f32759",
"selectionBackground": "#daf0ff"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Borland",
"black": "#4f4f4f",
"red": "#ff6c60",
"green": "#a8ff60",
"yellow": "#ffffb6",
"blue": "#96cbfe",
"purple": "#ff73fd",
"cyan": "#c6c5fe",
"white": "#eeeeee",
"brightBlack": "#7c7c7c",
"brightRed": "#ffb6b0",
"brightGreen": "#ceffac",
"brightYellow": "#ffffcc",
"brightBlue": "#b5dcff",
"brightPurple": "#ff9cfe",
"brightCyan": "#dfdffe",
"brightWhite": "#ffffff",
"background": "#0000a4",
"foreground": "#ffff4e",
"cursorColor": "#ffa560",
"selectionBackground": "#a4a4a4"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Box",
"black": "#000000",
"red": "#cc0403",
"green": "#19cb00",
"yellow": "#cecb00",
"blue": "#0d73cc",
"purple": "#cb1ed1",
"cyan": "#0dcdcd",
"white": "#dddddd",
"brightBlack": "#767676",
"brightRed": "#f2201f",
"brightGreen": "#23fd00",
"brightYellow": "#fffd00",
"brightBlue": "#1a8fff",
"brightPurple": "#fd28ff",
"brightCyan": "#14ffff",
"brightWhite": "#ffffff",
"background": "#141d2b",
"foreground": "#9fef00",
"cursorColor": "#9fef00",
"selectionBackground": "#a4b1cd"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Breadog",
"black": "#362c24",
"red": "#b10b00",
"green": "#007232",
"yellow": "#8b4c00",
"blue": "#005cb4",
"purple": "#9b0097",
"cyan": "#006a78",
"white": "#baa99d",
"brightBlack": "#514337",
"brightRed": "#de1100",
"brightGreen": "#008f40",
"brightYellow": "#ae6000",
"brightBlue": "#0074e1",
"brightPurple": "#c300bd",
"brightCyan": "#008697",
"brightWhite": "#eae1da",
"background": "#f1ebe6",
"foreground": "#362c24",
"cursorColor": "#362c24",
"selectionBackground": "#362c24"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Breeze",
"black": "#31363b",
"red": "#ed1515",
"green": "#11d116",
"yellow": "#f67400",
"blue": "#1d99f3",
"purple": "#9b59b6",
"cyan": "#1abc9c",
"white": "#eff0f1",
"brightBlack": "#7f8c8d",
"brightRed": "#c0392b",
"brightGreen": "#1cdc9a",
"brightYellow": "#fdbc4b",
"brightBlue": "#3daee9",
"brightPurple": "#8e44ad",
"brightCyan": "#16a085",
"brightWhite": "#fcfcfc",
"background": "#31363b",
"foreground": "#eff0f1",
"cursorColor": "#eff0f1",
"selectionBackground": "#eff0f1"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Bright Lights",
"black": "#191919",
"red": "#ff355b",
"green": "#b7e876",
"yellow": "#ffc251",
"blue": "#76d4ff",
"purple": "#ba76e7",
"cyan": "#6cbfb5",
"white": "#c2c8d7",
"brightBlack": "#4c4c4c",
"brightRed": "#ff355b",
"brightGreen": "#b7e876",
"brightYellow": "#ffc251",
"brightBlue": "#76d5ff",
"brightPurple": "#ba76e7",
"brightCyan": "#6cbfb5",
"brightWhite": "#c2c8d7",
"background": "#191919",
"foreground": "#b3c9d7",
"cursorColor": "#f34b00",
"selectionBackground": "#b3c9d7"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Broadcast",
"black": "#000000",
"red": "#da4939",
"green": "#519f50",
"yellow": "#ffd24a",
"blue": "#6d9cbe",
"purple": "#d0d0ff",
"cyan": "#6e9cbe",
"white": "#ffffff",
"brightBlack": "#585858",
"brightRed": "#ff7b6b",
"brightGreen": "#83d182",
"brightYellow": "#ffff7c",
"brightBlue": "#9fcef0",
"brightPurple": "#ffffff",
"brightCyan": "#a0cef0",
"brightWhite": "#ffffff",
"background": "#2b2b2b",
"foreground": "#e6e1dc",
"cursorColor": "#ffffff",
"selectionBackground": "#5a647e"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Brogrammer",
"black": "#1f1f1f",
"red": "#f81118",
"green": "#2dc55e",
"yellow": "#ecba0f",
"blue": "#2a84d2",
"purple": "#4e5ab7",
"cyan": "#1081d6",
"white": "#d6dbe5",
"brightBlack": "#d6dbe5",
"brightRed": "#de352e",
"brightGreen": "#1dd361",
"brightYellow": "#f3bd09",
"brightBlue": "#1081d6",
"brightPurple": "#5350b9",
"brightCyan": "#0f7ddb",
"brightWhite": "#ffffff",
"background": "#131313",
"foreground": "#d6dbe5",
"cursorColor": "#b9b9b9",
"selectionBackground": "#1f1f1f"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Builtin Dark",
"black": "#000000",
"red": "#bb0000",
"green": "#00bb00",
"yellow": "#bbbb00",
"blue": "#0d0dc8",
"purple": "#bb00bb",
"cyan": "#00bbbb",
"white": "#bbbbbb",
"brightBlack": "#555555",
"brightRed": "#ff5555",
"brightGreen": "#55ff55",
"brightYellow": "#ffff55",
"brightBlue": "#5555ff",
"brightPurple": "#ff55ff",
"brightCyan": "#55ffff",
"brightWhite": "#ffffff",
"background": "#000000",
"foreground": "#bbbbbb",
"cursorColor": "#bbbbbb",
"selectionBackground": "#b5d5ff"
}

Some files were not shown because too many files have changed in this diff Show More