Internal
Public Access
The RDP session pipeline never accounted for display scale: it
requested a desktop canvas sized in Qt logical pixels (never
multiplied by devicePixelRatio()), and FreeRDP_DesktopScaleFactor/
DeviceScaleFactor were read but never actually set anywhere. On a
HiDPI monitor this meant the remote session rendered assuming a
96 DPI / 100% display, and the resulting canvas got stretched
locally — ClearType's subpixel hinting doesn't survive that kind of
resampling, producing distorted glyph shapes and color fringing
rather than plain blur.
RdpDisplayWidget now reports physical pixel dimensions and the real
devicePixelRatio (recomputed on resize and on screen changes, e.g.
dragging the window to a different-DPI monitor). RdpSessionBackend
maps that to the nearest FreeRDP-legal scale value ({100, 140, 180},
per MS-RDPEDISP and FreeRDP's own reference client) and sets it at
both connect time and on every dynamic resize, including the
FreeRDP_MonitorOverrideFlags required for the values to actually be
honored rather than silently ignored.
While testing this against real infrastructure, found and fixed two
related (pre-existing, not caused by this change) resize issues:
- A stale-frame race where the old frame could be drawn at the wrong
scale for a moment after a resize, before a correctly-sized one
arrives — now the frame is cleared during that transition instead.
- No debounce on outgoing resize requests — every single resize event
fired an immediate request to the server, which can visibly
contribute to host-side redraw glitches during rapid layout churn
(e.g. right after connecting). Coalesced into one request per burst,
plus an explicit refresh-rect request after each resize completes
as a best-effort nudge for hosts that don't fully repaint on their
own.
A separate, deeper issue was also found during testing (the remote
guest's actual resolution sometimes not changing despite the resize
channel reporting success) and is tracked separately, not fixed here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
158 lines
5.1 KiB
C++
158 lines
5.1 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 QToolButton;
|
|
class QLineEdit;
|
|
class QComboBox;
|
|
class QLabel;
|
|
class QPushButton;
|
|
class KodoTerm;
|
|
|
|
struct SessionUiPreferences
|
|
{
|
|
QString terminalThemeName = QStringLiteral("Dark");
|
|
bool eventsPanelExpanded = false;
|
|
int terminalFontPointSize = 0;
|
|
};
|
|
|
|
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();
|
|
|
|
signals:
|
|
void tabTitleChanged(const QString& title);
|
|
void tabStateChanged(SessionState state);
|
|
void terminalThemeChanged(const QString& themeName);
|
|
void terminalFontSizeChanged(int pointSize);
|
|
void eventsPanelVisibilityChanged(bool expanded);
|
|
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);
|
|
|
|
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 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;
|
|
QString m_lastSyncedClipboardText;
|
|
bool m_clipboardSyncSupported;
|
|
|
|
KodoTerm* m_sshTerminal;
|
|
RdpDisplayWidget* m_rdpDisplay;
|
|
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_passwordPromptLabel;
|
|
QLineEdit* m_passwordPromptInput;
|
|
QPushButton* m_passwordPromptConnectButton;
|
|
QPushButton* m_passwordPromptCancelButton;
|
|
std::function<void(std::optional<QString>)> m_passwordPromptCallback;
|
|
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);
|
|
void hidePasswordPrompt();
|
|
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
|