Files
orbithub/src/session_tab.h
T
ksmithandClaude Sonnet 5 988637eb39 Add Quick Connect: open a session without saving a profile first
File > Quick Connect... and a matching Profiles-tab button open
ProfileDialog in a new quick-connect mode (Name/Tags/Folder hidden,
Name validation skipped) and hand the resulting in-memory profile
straight to a session tab -- nothing is written to the profile
database. The tab title is derived from host/username/protocol for
display only. A Quick Connect tab's context menu gets a "Save as
Profile..." action, shown only on tabs that were never saved, that
opens the normal profile dialog pre-filled to persist it afterward.

Bump version to v2026.9.28.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-28 16:48:00 -06:00

197 lines
7.0 KiB
C++

#ifndef ORBITHUB_SESSION_TAB_H
#define ORBITHUB_SESSION_TAB_H
#include "profile_repository.h"
#include "session_backend.h"
#include <QWidget>
#include <QStringList>
#include <QtGlobal>
#include <functional>
#include <optional>
#include <vector>
class QPlainTextEdit;
class QThread;
class SessionBackend;
class TerminalView;
class RdpDisplayWidget;
class VncDisplayWidget;
class QToolButton;
class QLineEdit;
class QComboBox;
class QLabel;
class QPushButton;
class QScrollArea;
class QShowEvent;
class KodoTerm;
struct SessionUiPreferences
{
QString terminalThemeName = QStringLiteral("Dark");
bool eventsPanelExpanded = false;
int terminalFontPointSize = 0;
bool vncScaleToFit = true;
};
class SessionTab : public QWidget
{
Q_OBJECT
public:
explicit SessionTab(const Profile& profile,
const SessionUiPreferences& preferences,
QWidget* parent = nullptr);
~SessionTab() override;
QString tabTitle() const;
void connectSession();
void disconnectSession();
void reconnectSession();
void clearTerminal();
void setTerminalThemeName(const QString& themeName);
QString terminalThemeName() const;
bool supportsThemeSelection() const;
bool supportsClearAction() const;
bool supportsZoom() const;
void zoomIn();
void zoomOut();
void resetZoom();
void setTerminalFontPointSize(int pointSize);
int terminalFontPointSize() const;
bool isEventsPanelExpanded() const;
void setEventsPanelExpanded(bool expanded);
void clearEvents();
void copyEvents() const;
void exportEventsToFile();
bool supportsVncScaleToggle() const;
void setVncScaleToFit(bool scaleToFit);
bool vncScaleToFit() const;
bool awaitingUserInput() const;
// True if this tab's profile was never saved to the repository (i.e.
// it came from Quick Connect, issue #23, rather than a saved profile).
// ProfileDialog::profile() always returns id <= 0 for an unsaved
// profile, and Quick Connect is the only path that hands such a
// profile to SessionWindow::addSessionTab() -- every other path looks
// the profile up from the repository first, which returns id > 0.
bool isQuickConnect() const;
Profile profileForSaving() const;
signals:
void tabTitleChanged(const QString& title);
void tabStateChanged(SessionState state);
// Fires whenever the inline username/password prompt bar is shown or
// hidden -- independent of tabStateChanged(), since the backend is
// still just "Connecting" while it's up. Lets SessionWindow mark a
// background tab that needs the user's attention (issue #22).
void awaitingUserInputChanged(bool waiting);
void terminalThemeChanged(const QString& themeName);
void terminalFontSizeChanged(int pointSize);
void eventsPanelVisibilityChanged(bool expanded);
void vncScaleModeChanged(bool scaleToFit);
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);
void requestDisplayScale(qreal devicePixelRatio);
void requestKeyEvent(int key,
quint32 nativeScanCode,
const QString& text,
bool pressed,
int modifiers);
void requestMouseMoveEvent(int x, int y);
void requestMouseButtonEvent(int x, int y, int button, bool pressed);
void requestMouseWheelEvent(int x, int y, int deltaX, int deltaY);
void requestSetClipboardText(const QString& text);
void requestProvideUsername(const QString& username);
protected:
// A tab that wasn't in front while the local clipboard changed missed
// that change (only the visible tab syncs), so catch it up on show.
void showEvent(QShowEvent* event) override;
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);
void onBackendUsernameRequested(const QString& prompt);
void onBackendRemoteClipboardTextChanged(const QString& text);
void onSystemClipboardChanged();
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;
int m_terminalFontPointSize;
// Text we just wrote to the system clipboard from a remote update, so
// the dataChanged() it triggers isn't echoed straight back out. Only
// honored briefly -- see onSystemClipboardChanged().
QString m_pendingClipboardEcho;
qint64 m_pendingClipboardEchoMs = 0;
bool m_clipboardSyncSupported;
KodoTerm* m_sshTerminal;
RdpDisplayWidget* m_rdpDisplay;
VncDisplayWidget* m_vncDisplay;
QScrollArea* m_vncScrollArea;
TerminalView* m_terminalOutput;
QPlainTextEdit* m_eventLog;
QToolButton* m_toggleEventsButton;
QLineEdit* m_eventFilterInput;
QComboBox* m_eventSeverityFilterInput;
QToolButton* m_clearEventsButton;
QToolButton* m_exportEventsButton;
QWidget* m_eventsPanel;
QWidget* m_passwordPromptBar;
QLabel* m_passwordPromptIcon;
QLabel* m_passwordPromptLabel;
QLineEdit* m_passwordPromptInput;
QPushButton* m_passwordPromptConnectButton;
QPushButton* m_passwordPromptCancelButton;
std::function<void(std::optional<QString>)> m_passwordPromptCallback;
bool m_awaitingUserInput;
enum class EventSeverity {
Info,
Warning,
Error,
};
struct EventEntry {
QString line;
EventSeverity severity;
};
std::vector<EventEntry> m_eventEntries;
QString m_eventFilter;
EventSeverity m_eventSeverityFilter;
bool m_eventsPanelExpanded;
void setupUi();
void requestConnectOptions(std::function<void(std::optional<SessionConnectOptions>)> callback);
void showPasswordPrompt(const QString& labelText,
std::function<void(std::optional<QString>)> callback,
bool maskInput = true);
void hidePasswordPrompt();
void pushLocalClipboardToRemote();
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);
void refreshEventLogView();
static EventSeverity classifyEventSeverity(const QString& message);
};
#endif