Internal
Public Access
RDP session fixes: correct keyboard scancodes, clipboard sync, cursor shapes
Fix RDP keyboard input using FreeRDP's authoritative X11-keycode-to-scancode table instead of ad hoc bit math, which misread punctuation keys as unrelated letter keys (e.g. apostrophe as B) because X11 keycode numbering only coincidentally overlaps PC/AT scancodes. Add bidirectional clipboard sync (CF_UNICODETEXT) over the cliprdr channel, and RDP pointer/cursor shape sync so the local cursor reflects what the remote OS wants displayed (resize handles, text I-beam, etc.) instead of staying a static arrow. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+2
-1
@@ -42,7 +42,8 @@ set(CHANNEL_DISP ON CACHE BOOL "" FORCE)
|
|||||||
set(CHANNEL_DISP_CLIENT ON CACHE BOOL "" FORCE)
|
set(CHANNEL_DISP_CLIENT ON CACHE BOOL "" FORCE)
|
||||||
set(CHANNEL_AINPUT OFF CACHE BOOL "" FORCE)
|
set(CHANNEL_AINPUT OFF CACHE BOOL "" FORCE)
|
||||||
set(CHANNEL_AUDIN OFF CACHE BOOL "" FORCE)
|
set(CHANNEL_AUDIN OFF CACHE BOOL "" FORCE)
|
||||||
set(CHANNEL_CLIPRDR OFF CACHE BOOL "" FORCE)
|
set(CHANNEL_CLIPRDR ON CACHE BOOL "" FORCE)
|
||||||
|
set(CHANNEL_CLIPRDR_CLIENT ON CACHE BOOL "" FORCE)
|
||||||
set(CHANNEL_DRIVE OFF CACHE BOOL "" FORCE)
|
set(CHANNEL_DRIVE OFF CACHE BOOL "" FORCE)
|
||||||
set(CHANNEL_ECHO OFF CACHE BOOL "" FORCE)
|
set(CHANNEL_ECHO OFF CACHE BOOL "" FORCE)
|
||||||
set(CHANNEL_ENCOMSP OFF CACHE BOOL "" FORCE)
|
set(CHANNEL_ENCOMSP OFF CACHE BOOL "" FORCE)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
#include "rdp_display_widget.h"
|
#include "rdp_display_widget.h"
|
||||||
|
|
||||||
|
#include <QCursor>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
#include <QPixmap>
|
||||||
#include <QResizeEvent>
|
#include <QResizeEvent>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QWheelEvent>
|
#include <QWheelEvent>
|
||||||
@@ -16,7 +18,7 @@ QSize sanitizeSize(const QSize& size)
|
|||||||
}
|
}
|
||||||
|
|
||||||
RdpDisplayWidget::RdpDisplayWidget(QWidget* parent)
|
RdpDisplayWidget::RdpDisplayWidget(QWidget* parent)
|
||||||
: QWidget(parent), m_remoteSize(1280, 720)
|
: QWidget(parent), m_remoteSize(1280, 720), m_cursorMode(CursorMode::Default)
|
||||||
{
|
{
|
||||||
setFocusPolicy(Qt::StrongFocus);
|
setFocusPolicy(Qt::StrongFocus);
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
@@ -61,6 +63,62 @@ void RdpDisplayWidget::clearFrame()
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RdpDisplayWidget::setCursorImage(const QImage& image, const QPoint& hotspot)
|
||||||
|
{
|
||||||
|
m_cursorImage = image;
|
||||||
|
m_cursorHotspot = hotspot;
|
||||||
|
m_cursorMode = CursorMode::Custom;
|
||||||
|
applyCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RdpDisplayWidget::setCursorHidden()
|
||||||
|
{
|
||||||
|
m_cursorMode = CursorMode::Hidden;
|
||||||
|
applyCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RdpDisplayWidget::setCursorDefault()
|
||||||
|
{
|
||||||
|
m_cursorMode = CursorMode::Default;
|
||||||
|
applyCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RdpDisplayWidget::applyCursor()
|
||||||
|
{
|
||||||
|
if (m_cursorMode == CursorMode::Hidden) {
|
||||||
|
setCursor(Qt::BlankCursor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_cursorMode == CursorMode::Default || m_cursorImage.isNull()) {
|
||||||
|
unsetCursor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QSize remote = effectiveRemoteSize();
|
||||||
|
const QRectF target = renderRect();
|
||||||
|
if (remote.isEmpty() || target.isEmpty()) {
|
||||||
|
setCursor(QCursor(QPixmap::fromImage(m_cursorImage),
|
||||||
|
m_cursorHotspot.x(),
|
||||||
|
m_cursorHotspot.y()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const qreal scale = target.width() / remote.width();
|
||||||
|
QImage scaledImage = m_cursorImage;
|
||||||
|
if (!qFuzzyCompare(scale, 1.0)) {
|
||||||
|
scaledImage = m_cursorImage.scaled(
|
||||||
|
qMax(1, qRound(m_cursorImage.width() * scale)),
|
||||||
|
qMax(1, qRound(m_cursorImage.height() * scale)),
|
||||||
|
Qt::IgnoreAspectRatio,
|
||||||
|
Qt::SmoothTransformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int hotX = qBound(0, qRound(m_cursorHotspot.x() * scale), scaledImage.width());
|
||||||
|
const int hotY = qBound(0, qRound(m_cursorHotspot.y() * scale), scaledImage.height());
|
||||||
|
setCursor(QCursor(QPixmap::fromImage(scaledImage), hotX, hotY));
|
||||||
|
}
|
||||||
|
|
||||||
void RdpDisplayWidget::paintEvent(QPaintEvent* event)
|
void RdpDisplayWidget::paintEvent(QPaintEvent* event)
|
||||||
{
|
{
|
||||||
Q_UNUSED(event);
|
Q_UNUSED(event);
|
||||||
@@ -84,6 +142,7 @@ void RdpDisplayWidget::resizeEvent(QResizeEvent* event)
|
|||||||
QWidget::resizeEvent(event);
|
QWidget::resizeEvent(event);
|
||||||
const QSize size = sanitizeSize(event->size());
|
const QSize size = sanitizeSize(event->size());
|
||||||
emit viewportSizeChanged(size.width(), size.height());
|
emit viewportSizeChanged(size.width(), size.height());
|
||||||
|
applyCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RdpDisplayWidget::keyPressEvent(QKeyEvent* event)
|
void RdpDisplayWidget::keyPressEvent(QKeyEvent* event)
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ public:
|
|||||||
void setFrame(const QImage& frame);
|
void setFrame(const QImage& frame);
|
||||||
void setRemoteDesktopSize(int width, int height);
|
void setRemoteDesktopSize(int width, int height);
|
||||||
void clearFrame();
|
void clearFrame();
|
||||||
|
void setCursorImage(const QImage& image, const QPoint& hotspot);
|
||||||
|
void setCursorHidden();
|
||||||
|
void setCursorDefault();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void keyInput(int key, quint32 nativeScanCode, const QString& text, bool pressed, int modifiers);
|
void keyInput(int key, quint32 nativeScanCode, const QString& text, bool pressed, int modifiers);
|
||||||
@@ -39,12 +42,22 @@ protected:
|
|||||||
void wheelEvent(QWheelEvent* event) override;
|
void wheelEvent(QWheelEvent* event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
enum class CursorMode {
|
||||||
|
Default,
|
||||||
|
Custom,
|
||||||
|
Hidden,
|
||||||
|
};
|
||||||
|
|
||||||
QImage m_frame;
|
QImage m_frame;
|
||||||
QSize m_remoteSize;
|
QSize m_remoteSize;
|
||||||
|
QImage m_cursorImage;
|
||||||
|
QPoint m_cursorHotspot;
|
||||||
|
CursorMode m_cursorMode;
|
||||||
|
|
||||||
QRectF renderRect() const;
|
QRectF renderRect() const;
|
||||||
QPoint mapToRemote(const QPointF& pos) const;
|
QPoint mapToRemote(const QPointF& pos) const;
|
||||||
QSize effectiveRemoteSize() const;
|
QSize effectiveRemoteSize() const;
|
||||||
|
void applyCursor();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+477
-25
@@ -7,26 +7,33 @@
|
|||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <new>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#ifdef ORBITHUB_HAS_FREERDP
|
#ifdef ORBITHUB_HAS_FREERDP
|
||||||
#include <freerdp/channels/channels.h>
|
#include <freerdp/channels/channels.h>
|
||||||
|
#include <freerdp/channels/cliprdr.h>
|
||||||
#include <freerdp/channels/drdynvc.h>
|
#include <freerdp/channels/drdynvc.h>
|
||||||
#include <freerdp/channels/disp.h>
|
#include <freerdp/channels/disp.h>
|
||||||
|
#include <freerdp/client/cliprdr.h>
|
||||||
#include <freerdp/addin.h>
|
#include <freerdp/addin.h>
|
||||||
#include <freerdp/client/channels.h>
|
#include <freerdp/client/channels.h>
|
||||||
#include <freerdp/client/cmdline.h>
|
#include <freerdp/client/cmdline.h>
|
||||||
#include <freerdp/client/disp.h>
|
#include <freerdp/client/disp.h>
|
||||||
|
#include <freerdp/codec/color.h>
|
||||||
#include <freerdp/display.h>
|
#include <freerdp/display.h>
|
||||||
#include <freerdp/error.h>
|
#include <freerdp/error.h>
|
||||||
#include <freerdp/event.h>
|
#include <freerdp/event.h>
|
||||||
#include <freerdp/freerdp.h>
|
#include <freerdp/freerdp.h>
|
||||||
#include <freerdp/gdi/gdi.h>
|
#include <freerdp/gdi/gdi.h>
|
||||||
|
#include <freerdp/graphics.h>
|
||||||
#include <freerdp/input.h>
|
#include <freerdp/input.h>
|
||||||
|
#include <freerdp/locale/keyboard.h>
|
||||||
#include <freerdp/scancode.h>
|
#include <freerdp/scancode.h>
|
||||||
#include <freerdp/settings.h>
|
#include <freerdp/settings.h>
|
||||||
#include <winpr/crt.h>
|
#include <winpr/crt.h>
|
||||||
#include <winpr/synch.h>
|
#include <winpr/synch.h>
|
||||||
|
#include <winpr/user.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -146,6 +153,16 @@ UINT orbitDisplayControlCaps(DispClientContext* context,
|
|||||||
UINT32 maxNumMonitors,
|
UINT32 maxNumMonitors,
|
||||||
UINT32 maxMonitorAreaFactorA,
|
UINT32 maxMonitorAreaFactorA,
|
||||||
UINT32 maxMonitorAreaFactorB);
|
UINT32 maxMonitorAreaFactorB);
|
||||||
|
UINT orbitCliprdrMonitorReady(CliprdrClientContext* context,
|
||||||
|
const CLIPRDR_MONITOR_READY* monitorReady);
|
||||||
|
UINT orbitCliprdrServerFormatList(CliprdrClientContext* context,
|
||||||
|
const CLIPRDR_FORMAT_LIST* formatList);
|
||||||
|
UINT orbitCliprdrServerFormatListResponse(
|
||||||
|
CliprdrClientContext* context, const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse);
|
||||||
|
UINT orbitCliprdrServerFormatDataRequest(
|
||||||
|
CliprdrClientContext* context, const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest);
|
||||||
|
UINT orbitCliprdrServerFormatDataResponse(
|
||||||
|
CliprdrClientContext* context, const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse);
|
||||||
BOOL orbitLoadChannels(freerdp* instance);
|
BOOL orbitLoadChannels(freerdp* instance);
|
||||||
bool orbitLoadStaticChannel(rdpChannels* channels,
|
bool orbitLoadStaticChannel(rdpChannels* channels,
|
||||||
rdpSettings* settings,
|
rdpSettings* settings,
|
||||||
@@ -246,6 +263,118 @@ BOOL orbitDesktopResize(rdpContext* context)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom rdpPointer subclass: FreeRDP allocates this with calloc() and only
|
||||||
|
// ever touches the base rdpPointer fields itself, so the trailing fields here
|
||||||
|
// must stay plain-old-data and be managed manually in New()/Free() (mirrors
|
||||||
|
// FreeRDP's own X11 client, client/X11/xf_graphics.c).
|
||||||
|
struct OrbitRdpPointer
|
||||||
|
{
|
||||||
|
rdpPointer pointer;
|
||||||
|
uchar* rgbaData;
|
||||||
|
UINT32 rgbaWidth;
|
||||||
|
UINT32 rgbaHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
BOOL orbitPointerNew(rdpContext* context, rdpPointer* pointer)
|
||||||
|
{
|
||||||
|
if (context == nullptr || pointer == nullptr || context->gdi == nullptr) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* orbitPointer = reinterpret_cast<OrbitRdpPointer*>(pointer);
|
||||||
|
orbitPointer->rgbaData = nullptr;
|
||||||
|
orbitPointer->rgbaWidth = 0;
|
||||||
|
orbitPointer->rgbaHeight = 0;
|
||||||
|
|
||||||
|
if (pointer->width == 0 || pointer->height == 0) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t size = static_cast<size_t>(pointer->width) * pointer->height * 4;
|
||||||
|
auto* buffer = new (std::nothrow) uchar[size];
|
||||||
|
if (buffer == nullptr) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!freerdp_image_copy_from_pointer_data(buffer,
|
||||||
|
PIXEL_FORMAT_BGRA32,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
pointer->width,
|
||||||
|
pointer->height,
|
||||||
|
pointer->xorMaskData,
|
||||||
|
pointer->lengthXorMask,
|
||||||
|
pointer->andMaskData,
|
||||||
|
pointer->lengthAndMask,
|
||||||
|
pointer->xorBpp,
|
||||||
|
&context->gdi->palette)) {
|
||||||
|
delete[] buffer;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
orbitPointer->rgbaData = buffer;
|
||||||
|
orbitPointer->rgbaWidth = pointer->width;
|
||||||
|
orbitPointer->rgbaHeight = pointer->height;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void orbitPointerFree(rdpContext* context, rdpPointer* pointer)
|
||||||
|
{
|
||||||
|
Q_UNUSED(context);
|
||||||
|
if (pointer == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* orbitPointer = reinterpret_cast<OrbitRdpPointer*>(pointer);
|
||||||
|
delete[] orbitPointer->rgbaData;
|
||||||
|
orbitPointer->rgbaData = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL orbitPointerSet(rdpContext* context, rdpPointer* pointer)
|
||||||
|
{
|
||||||
|
if (context == nullptr || pointer == nullptr) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
RdpSessionBackend* backend = backendFromContext(context);
|
||||||
|
if (backend == nullptr) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* orbitPointer = reinterpret_cast<OrbitRdpPointer*>(pointer);
|
||||||
|
if (orbitPointer->rgbaData == nullptr || orbitPointer->rgbaWidth == 0
|
||||||
|
|| orbitPointer->rgbaHeight == 0) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QImage image(orbitPointer->rgbaData,
|
||||||
|
static_cast<int>(orbitPointer->rgbaWidth),
|
||||||
|
static_cast<int>(orbitPointer->rgbaHeight),
|
||||||
|
QImage::Format_ARGB32);
|
||||||
|
|
||||||
|
emit backend->cursorImageChanged(
|
||||||
|
image.copy(),
|
||||||
|
QPoint(static_cast<int>(pointer->xPos), static_cast<int>(pointer->yPos)));
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL orbitPointerSetNull(rdpContext* context)
|
||||||
|
{
|
||||||
|
if (RdpSessionBackend* backend = backendFromContext(context)) {
|
||||||
|
emit backend->cursorHidden();
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL orbitPointerSetDefault(rdpContext* context)
|
||||||
|
{
|
||||||
|
if (RdpSessionBackend* backend = backendFromContext(context)) {
|
||||||
|
emit backend->cursorReset();
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
BOOL orbitPreConnect(freerdp* instance)
|
BOOL orbitPreConnect(freerdp* instance)
|
||||||
{
|
{
|
||||||
if (instance == nullptr || instance->context == nullptr || instance->context->settings == nullptr) {
|
if (instance == nullptr || instance->context == nullptr || instance->context->settings == nullptr) {
|
||||||
@@ -274,6 +403,17 @@ BOOL orbitPostConnect(freerdp* instance)
|
|||||||
context->update->EndPaint = orbitEndPaint;
|
context->update->EndPaint = orbitEndPaint;
|
||||||
context->update->DesktopResize = orbitDesktopResize;
|
context->update->DesktopResize = orbitDesktopResize;
|
||||||
|
|
||||||
|
if (context->graphics != nullptr) {
|
||||||
|
rdpPointer pointerCallbacks = {};
|
||||||
|
pointerCallbacks.size = sizeof(OrbitRdpPointer);
|
||||||
|
pointerCallbacks.New = orbitPointerNew;
|
||||||
|
pointerCallbacks.Free = orbitPointerFree;
|
||||||
|
pointerCallbacks.Set = orbitPointerSet;
|
||||||
|
pointerCallbacks.SetNull = orbitPointerSetNull;
|
||||||
|
pointerCallbacks.SetDefault = orbitPointerSetDefault;
|
||||||
|
graphics_register_pointer(context->graphics, &pointerCallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
if (RdpSessionBackend* backend = backendFromContext(context)) {
|
if (RdpSessionBackend* backend = backendFromContext(context)) {
|
||||||
const int width = static_cast<int>(
|
const int width = static_cast<int>(
|
||||||
freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth));
|
freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth));
|
||||||
@@ -291,6 +431,7 @@ void orbitPostDisconnect(freerdp* instance)
|
|||||||
if (instance->context != nullptr) {
|
if (instance->context != nullptr) {
|
||||||
if (RdpSessionBackend* backend = backendFromContext(instance->context)) {
|
if (RdpSessionBackend* backend = backendFromContext(instance->context)) {
|
||||||
backend->onChannelDisconnectedEvent(DISP_DVC_CHANNEL_NAME, nullptr);
|
backend->onChannelDisconnectedEvent(DISP_DVC_CHANNEL_NAME, nullptr);
|
||||||
|
backend->onChannelDisconnectedEvent(CLIPRDR_SVC_CHANNEL_NAME, nullptr);
|
||||||
}
|
}
|
||||||
PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
|
PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
|
||||||
orbitOnChannelConnectedEventHandler);
|
orbitOnChannelConnectedEventHandler);
|
||||||
@@ -337,6 +478,75 @@ UINT orbitDisplayControlCaps(DispClientContext* context,
|
|||||||
return CHANNEL_RC_OK;
|
return CHANNEL_RC_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UINT orbitCliprdrMonitorReady(CliprdrClientContext* context,
|
||||||
|
const CLIPRDR_MONITOR_READY* monitorReady)
|
||||||
|
{
|
||||||
|
Q_UNUSED(monitorReady);
|
||||||
|
if (context == nullptr || context->custom == nullptr) {
|
||||||
|
return CHANNEL_RC_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* backend = reinterpret_cast<RdpSessionBackend*>(context->custom);
|
||||||
|
backend->onCliprdrMonitorReady();
|
||||||
|
return CHANNEL_RC_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT orbitCliprdrServerFormatList(CliprdrClientContext* context,
|
||||||
|
const CLIPRDR_FORMAT_LIST* formatList)
|
||||||
|
{
|
||||||
|
if (context == nullptr || context->custom == nullptr || formatList == nullptr) {
|
||||||
|
return CHANNEL_RC_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasUnicodeText = false;
|
||||||
|
for (UINT32 i = 0; i < formatList->numFormats; ++i) {
|
||||||
|
if (formatList->formats[i].formatId == CF_UNICODETEXT) {
|
||||||
|
hasUnicodeText = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* backend = reinterpret_cast<RdpSessionBackend*>(context->custom);
|
||||||
|
backend->onCliprdrServerFormatList(hasUnicodeText);
|
||||||
|
return CHANNEL_RC_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT orbitCliprdrServerFormatListResponse(
|
||||||
|
CliprdrClientContext* context, const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse)
|
||||||
|
{
|
||||||
|
Q_UNUSED(context);
|
||||||
|
Q_UNUSED(formatListResponse);
|
||||||
|
return CHANNEL_RC_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT orbitCliprdrServerFormatDataRequest(
|
||||||
|
CliprdrClientContext* context, const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest)
|
||||||
|
{
|
||||||
|
if (context == nullptr || context->custom == nullptr || formatDataRequest == nullptr) {
|
||||||
|
return CHANNEL_RC_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* backend = reinterpret_cast<RdpSessionBackend*>(context->custom);
|
||||||
|
backend->onCliprdrServerFormatDataRequest(formatDataRequest->requestedFormatId);
|
||||||
|
return CHANNEL_RC_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT orbitCliprdrServerFormatDataResponse(
|
||||||
|
CliprdrClientContext* context, const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse)
|
||||||
|
{
|
||||||
|
if (context == nullptr || context->custom == nullptr || formatDataResponse == nullptr) {
|
||||||
|
return CHANNEL_RC_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* backend = reinterpret_cast<RdpSessionBackend*>(context->custom);
|
||||||
|
const bool success = (formatDataResponse->common.msgFlags & CB_RESPONSE_OK) != 0;
|
||||||
|
backend->onCliprdrServerFormatDataResponse(
|
||||||
|
success,
|
||||||
|
reinterpret_cast<const uint8_t*>(formatDataResponse->requestedFormatData),
|
||||||
|
formatDataResponse->common.dataLen);
|
||||||
|
return CHANNEL_RC_OK;
|
||||||
|
}
|
||||||
|
|
||||||
BOOL orbitLoadChannels(freerdp* instance)
|
BOOL orbitLoadChannels(freerdp* instance)
|
||||||
{
|
{
|
||||||
if (instance == nullptr || instance->context == nullptr || instance->context->settings == nullptr
|
if (instance == nullptr || instance->context == nullptr || instance->context->settings == nullptr
|
||||||
@@ -383,6 +593,14 @@ BOOL orbitLoadChannels(freerdp* instance)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!orbitLoadStaticChannel(channels, settings, CLIPRDR_SVC_CHANNEL_NAME, settings)) {
|
||||||
|
if (backend != nullptr) {
|
||||||
|
emit backend->eventLogged(
|
||||||
|
QStringLiteral("RDP warning: failed to load static '%1' channel; clipboard sync unavailable.")
|
||||||
|
.arg(QString::fromUtf8(CLIPRDR_SVC_CHANNEL_NAME)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (backend != nullptr) {
|
if (backend != nullptr) {
|
||||||
emit backend->eventLogged(QStringLiteral("RDP channel loader: display-control channels initialized."));
|
emit backend->eventLogged(QStringLiteral("RDP channel loader: display-control channels initialized."));
|
||||||
}
|
}
|
||||||
@@ -576,6 +794,25 @@ UINT32 scancodeFromNativeScanCode(quint32 nativeScanCode)
|
|||||||
return RDP_SCANCODE_UNKNOWN;
|
return RDP_SCANCODE_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(Q_OS_LINUX)
|
||||||
|
// On X11, Qt's nativeScanCode() is the raw X11 KeyCode, not a PC/AT set-1
|
||||||
|
// scancode. X11 keycode numbering only coincidentally overlaps with PC/AT
|
||||||
|
// scancodes for some keys and diverges for others (e.g. X11 keycode 0x30
|
||||||
|
// is the apostrophe/quote key, while PC/AT scancode 0x30 is the B key), so
|
||||||
|
// treating one as the other silently sends the wrong key. FreeRDP ships an
|
||||||
|
// authoritative X11-keycode -> RDP-scancode table; use it directly.
|
||||||
|
// The function is marked deprecated upstream ("implement yourself in
|
||||||
|
// client") but remains present and correct in this pinned FreeRDP build.
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
#endif
|
||||||
|
const DWORD mapped = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(nativeScanCode);
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
#endif
|
||||||
|
return mapped != 0 ? mapped : RDP_SCANCODE_UNKNOWN;
|
||||||
|
#else
|
||||||
quint32 code = 0;
|
quint32 code = 0;
|
||||||
bool extended = false;
|
bool extended = false;
|
||||||
if ((nativeScanCode & 0xFF000000u) == 0xE0000000u) {
|
if ((nativeScanCode & 0xFF000000u) == 0xE0000000u) {
|
||||||
@@ -607,6 +844,7 @@ UINT32 scancodeFromNativeScanCode(quint32 nativeScanCode)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return scancode;
|
return scancode;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
UINT32 scancodeForQtKey(int key, Qt::KeyboardModifiers modifiers, quint32 nativeScanCode)
|
UINT32 scancodeForQtKey(int key, Qt::KeyboardModifiers modifiers, quint32 nativeScanCode)
|
||||||
@@ -1001,7 +1239,8 @@ RdpSessionBackend::RdpSessionBackend(const Profile& profile, QObject* parent)
|
|||||||
m_displayControlReady(false),
|
m_displayControlReady(false),
|
||||||
m_resizeFailureLogged(false),
|
m_resizeFailureLogged(false),
|
||||||
m_lastResizeWidth(0),
|
m_lastResizeWidth(0),
|
||||||
m_lastResizeHeight(0)
|
m_lastResizeHeight(0),
|
||||||
|
m_cliprdrContext(nullptr)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1112,6 +1351,18 @@ void RdpSessionBackend::sendKeyEvent(int key,
|
|||||||
enqueueInputEvent(event);
|
enqueueInputEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RdpSessionBackend::setClipboardText(const QString& text)
|
||||||
|
{
|
||||||
|
if (!m_workerRunning.load()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputEvent event;
|
||||||
|
event.type = InputEventType::SetClipboardText;
|
||||||
|
event.text = text;
|
||||||
|
enqueueInputEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
void RdpSessionBackend::sendMouseMoveEvent(int x, int y)
|
void RdpSessionBackend::sendMouseMoveEvent(int x, int y)
|
||||||
{
|
{
|
||||||
if (!m_workerRunning.load()) {
|
if (!m_workerRunning.load()) {
|
||||||
@@ -1537,6 +1788,38 @@ bool RdpSessionBackend::sendDisplayResize(rdp_freerdp* instance, int width, int
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RdpSessionBackend::sendClipboardTextToRemote(rdp_freerdp* instance, const QString& text)
|
||||||
|
{
|
||||||
|
#ifndef ORBITHUB_HAS_FREERDP
|
||||||
|
Q_UNUSED(instance);
|
||||||
|
Q_UNUSED(text);
|
||||||
|
#else
|
||||||
|
Q_UNUSED(instance);
|
||||||
|
|
||||||
|
CliprdrClientContext* cliprdrContext = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_cliprdrMutex);
|
||||||
|
m_pendingLocalClipboardText = text;
|
||||||
|
cliprdrContext = reinterpret_cast<CliprdrClientContext*>(m_cliprdrContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cliprdrContext == nullptr || cliprdrContext->ClientFormatList == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLIPRDR_FORMAT format = {};
|
||||||
|
format.formatId = CF_UNICODETEXT;
|
||||||
|
format.formatName = nullptr;
|
||||||
|
|
||||||
|
CLIPRDR_FORMAT_LIST formatList = {};
|
||||||
|
formatList.numFormats = 1;
|
||||||
|
formatList.formats = &format;
|
||||||
|
|
||||||
|
const UINT rc = cliprdrContext->ClientFormatList(cliprdrContext, &formatList);
|
||||||
|
Q_UNUSED(rc);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void RdpSessionBackend::processInputEvents(rdp_freerdp* instance)
|
void RdpSessionBackend::processInputEvents(rdp_freerdp* instance)
|
||||||
{
|
{
|
||||||
#ifndef ORBITHUB_HAS_FREERDP
|
#ifndef ORBITHUB_HAS_FREERDP
|
||||||
@@ -1661,6 +1944,9 @@ void RdpSessionBackend::processInputEvents(rdp_freerdp* instance)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case InputEventType::SetClipboardText:
|
||||||
|
sendClipboardTextToRemote(instance, event.text);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1686,24 +1972,47 @@ void RdpSessionBackend::processInputEvents(rdp_freerdp* instance)
|
|||||||
void RdpSessionBackend::onChannelConnectedEvent(const char* name, void* channelInterface)
|
void RdpSessionBackend::onChannelConnectedEvent(const char* name, void* channelInterface)
|
||||||
{
|
{
|
||||||
#ifdef ORBITHUB_HAS_FREERDP
|
#ifdef ORBITHUB_HAS_FREERDP
|
||||||
if (name == nullptr || std::strcmp(name, DISP_DVC_CHANNEL_NAME) != 0) {
|
if (name == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* dispContext = reinterpret_cast<DispClientContext*>(channelInterface);
|
if (std::strcmp(name, DISP_DVC_CHANNEL_NAME) == 0) {
|
||||||
if (dispContext == nullptr) {
|
auto* dispContext = reinterpret_cast<DispClientContext*>(channelInterface);
|
||||||
|
if (dispContext == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispContext->custom = this;
|
||||||
|
dispContext->DisplayControlCaps = orbitDisplayControlCaps;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_displayControlMutex);
|
||||||
|
m_displayControlContext = dispContext;
|
||||||
|
m_displayControlReady = false;
|
||||||
|
}
|
||||||
|
emit eventLogged(QStringLiteral("RDP display-control channel connected."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispContext->custom = this;
|
if (std::strcmp(name, CLIPRDR_SVC_CHANNEL_NAME) == 0) {
|
||||||
dispContext->DisplayControlCaps = orbitDisplayControlCaps;
|
auto* cliprdrContext = reinterpret_cast<CliprdrClientContext*>(channelInterface);
|
||||||
|
if (cliprdrContext == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
{
|
cliprdrContext->custom = this;
|
||||||
std::lock_guard<std::mutex> guard(m_displayControlMutex);
|
cliprdrContext->MonitorReady = orbitCliprdrMonitorReady;
|
||||||
m_displayControlContext = dispContext;
|
cliprdrContext->ServerFormatList = orbitCliprdrServerFormatList;
|
||||||
m_displayControlReady = false;
|
cliprdrContext->ServerFormatListResponse = orbitCliprdrServerFormatListResponse;
|
||||||
|
cliprdrContext->ServerFormatDataRequest = orbitCliprdrServerFormatDataRequest;
|
||||||
|
cliprdrContext->ServerFormatDataResponse = orbitCliprdrServerFormatDataResponse;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_cliprdrMutex);
|
||||||
|
m_cliprdrContext = cliprdrContext;
|
||||||
|
}
|
||||||
|
emit eventLogged(QStringLiteral("RDP clipboard channel connected."));
|
||||||
}
|
}
|
||||||
emit eventLogged(QStringLiteral("RDP display-control channel connected."));
|
|
||||||
#else
|
#else
|
||||||
Q_UNUSED(name);
|
Q_UNUSED(name);
|
||||||
Q_UNUSED(channelInterface);
|
Q_UNUSED(channelInterface);
|
||||||
@@ -1713,24 +2022,43 @@ void RdpSessionBackend::onChannelConnectedEvent(const char* name, void* channelI
|
|||||||
void RdpSessionBackend::onChannelDisconnectedEvent(const char* name, void* channelInterface)
|
void RdpSessionBackend::onChannelDisconnectedEvent(const char* name, void* channelInterface)
|
||||||
{
|
{
|
||||||
#ifdef ORBITHUB_HAS_FREERDP
|
#ifdef ORBITHUB_HAS_FREERDP
|
||||||
if (name != nullptr && std::strcmp(name, DISP_DVC_CHANNEL_NAME) != 0) {
|
const bool matchesDisp = name == nullptr || std::strcmp(name, DISP_DVC_CHANNEL_NAME) == 0;
|
||||||
return;
|
const bool matchesCliprdr =
|
||||||
}
|
name == nullptr || std::strcmp(name, CLIPRDR_SVC_CHANNEL_NAME) == 0;
|
||||||
|
|
||||||
bool cleared = false;
|
if (matchesDisp) {
|
||||||
bool hadDisplayControl = false;
|
bool cleared = false;
|
||||||
{
|
bool hadDisplayControl = false;
|
||||||
std::lock_guard<std::mutex> guard(m_displayControlMutex);
|
{
|
||||||
hadDisplayControl = (m_displayControlContext != nullptr) || m_displayControlReady;
|
std::lock_guard<std::mutex> guard(m_displayControlMutex);
|
||||||
if (channelInterface == nullptr || m_displayControlContext == channelInterface) {
|
hadDisplayControl = (m_displayControlContext != nullptr) || m_displayControlReady;
|
||||||
m_displayControlContext = nullptr;
|
if (channelInterface == nullptr || m_displayControlContext == channelInterface) {
|
||||||
m_displayControlReady = false;
|
m_displayControlContext = nullptr;
|
||||||
cleared = true;
|
m_displayControlReady = false;
|
||||||
|
cleared = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleared && hadDisplayControl) {
|
||||||
|
emit eventLogged(QStringLiteral("RDP display-control channel disconnected."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cleared && hadDisplayControl) {
|
if (matchesCliprdr) {
|
||||||
emit eventLogged(QStringLiteral("RDP display-control channel disconnected."));
|
bool cleared = false;
|
||||||
|
bool hadCliprdr = false;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_cliprdrMutex);
|
||||||
|
hadCliprdr = m_cliprdrContext != nullptr;
|
||||||
|
if (channelInterface == nullptr || m_cliprdrContext == channelInterface) {
|
||||||
|
m_cliprdrContext = nullptr;
|
||||||
|
cleared = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleared && hadCliprdr) {
|
||||||
|
emit eventLogged(QStringLiteral("RDP clipboard channel disconnected."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
Q_UNUSED(name);
|
Q_UNUSED(name);
|
||||||
@@ -1769,6 +2097,130 @@ void RdpSessionBackend::onDisplayControlCaps(uint32_t maxNumMonitors,
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RdpSessionBackend::onCliprdrMonitorReady()
|
||||||
|
{
|
||||||
|
#ifdef ORBITHUB_HAS_FREERDP
|
||||||
|
CliprdrClientContext* cliprdrContext = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_cliprdrMutex);
|
||||||
|
cliprdrContext = reinterpret_cast<CliprdrClientContext*>(m_cliprdrContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cliprdrContext == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = {};
|
||||||
|
generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL;
|
||||||
|
generalCapabilitySet.capabilitySetLength = CB_CAPSTYPE_GENERAL_LEN;
|
||||||
|
generalCapabilitySet.version = CB_CAPS_VERSION_2;
|
||||||
|
generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES;
|
||||||
|
|
||||||
|
CLIPRDR_CAPABILITIES capabilities = {};
|
||||||
|
capabilities.cCapabilitiesSets = 1;
|
||||||
|
capabilities.capabilitySets = reinterpret_cast<CLIPRDR_CAPABILITY_SET*>(&generalCapabilitySet);
|
||||||
|
|
||||||
|
if (cliprdrContext->ClientCapabilities != nullptr) {
|
||||||
|
const UINT rc = cliprdrContext->ClientCapabilities(cliprdrContext, &capabilities);
|
||||||
|
Q_UNUSED(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit eventLogged(QStringLiteral("RDP clipboard channel ready."));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void RdpSessionBackend::onCliprdrServerFormatList(bool hasUnicodeText)
|
||||||
|
{
|
||||||
|
#ifdef ORBITHUB_HAS_FREERDP
|
||||||
|
CliprdrClientContext* cliprdrContext = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_cliprdrMutex);
|
||||||
|
cliprdrContext = reinterpret_cast<CliprdrClientContext*>(m_cliprdrContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cliprdrContext == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cliprdrContext->ClientFormatListResponse != nullptr) {
|
||||||
|
CLIPRDR_FORMAT_LIST_RESPONSE response = {};
|
||||||
|
response.common.msgFlags = CB_RESPONSE_OK;
|
||||||
|
const UINT rc = cliprdrContext->ClientFormatListResponse(cliprdrContext, &response);
|
||||||
|
Q_UNUSED(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasUnicodeText && cliprdrContext->ClientFormatDataRequest != nullptr) {
|
||||||
|
CLIPRDR_FORMAT_DATA_REQUEST request = {};
|
||||||
|
request.requestedFormatId = CF_UNICODETEXT;
|
||||||
|
const UINT rc = cliprdrContext->ClientFormatDataRequest(cliprdrContext, &request);
|
||||||
|
Q_UNUSED(rc);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
Q_UNUSED(hasUnicodeText);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void RdpSessionBackend::onCliprdrServerFormatDataRequest(uint32_t requestedFormatId)
|
||||||
|
{
|
||||||
|
#ifdef ORBITHUB_HAS_FREERDP
|
||||||
|
CliprdrClientContext* cliprdrContext = nullptr;
|
||||||
|
QString pendingText;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_cliprdrMutex);
|
||||||
|
cliprdrContext = reinterpret_cast<CliprdrClientContext*>(m_cliprdrContext);
|
||||||
|
pendingText = m_pendingLocalClipboardText;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cliprdrContext == nullptr || cliprdrContext->ClientFormatDataResponse == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLIPRDR_FORMAT_DATA_RESPONSE response = {};
|
||||||
|
QByteArray encoded;
|
||||||
|
if (requestedFormatId == CF_UNICODETEXT) {
|
||||||
|
const int byteLength = (pendingText.size() + 1) * static_cast<int>(sizeof(ushort));
|
||||||
|
encoded = QByteArray(reinterpret_cast<const char*>(pendingText.utf16()), byteLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.common.msgFlags = encoded.isEmpty() ? CB_RESPONSE_FAIL : CB_RESPONSE_OK;
|
||||||
|
response.common.dataLen = static_cast<UINT32>(encoded.size());
|
||||||
|
response.requestedFormatData =
|
||||||
|
encoded.isEmpty() ? nullptr : reinterpret_cast<const BYTE*>(encoded.constData());
|
||||||
|
const UINT rc = cliprdrContext->ClientFormatDataResponse(cliprdrContext, &response);
|
||||||
|
Q_UNUSED(rc);
|
||||||
|
#else
|
||||||
|
Q_UNUSED(requestedFormatId);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void RdpSessionBackend::onCliprdrServerFormatDataResponse(bool success,
|
||||||
|
const uint8_t* data,
|
||||||
|
uint32_t size)
|
||||||
|
{
|
||||||
|
#ifdef ORBITHUB_HAS_FREERDP
|
||||||
|
if (!success || data == nullptr || size == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* utf16Data = reinterpret_cast<const char16_t*>(data);
|
||||||
|
const size_t charCount = size / sizeof(char16_t);
|
||||||
|
|
||||||
|
// CF_UNICODETEXT is NUL-terminated per MS-RDPECLIP; trim at the first NUL
|
||||||
|
// rather than trusting dataLen to exclude it.
|
||||||
|
size_t length = 0;
|
||||||
|
while (length < charCount && utf16Data[length] != 0) {
|
||||||
|
++length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString text = QString::fromUtf16(utf16Data, static_cast<int>(length));
|
||||||
|
emit remoteClipboardTextChanged(text);
|
||||||
|
#else
|
||||||
|
Q_UNUSED(success);
|
||||||
|
Q_UNUSED(data);
|
||||||
|
Q_UNUSED(size);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void RdpSessionBackend::emitStateAsync(SessionState state, const QString& message)
|
void RdpSessionBackend::emitStateAsync(SessionState state, const QString& message)
|
||||||
{
|
{
|
||||||
QMetaObject::invokeMethod(
|
QMetaObject::invokeMethod(
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public slots:
|
|||||||
void sendMouseMoveEvent(int x, int y) override;
|
void sendMouseMoveEvent(int x, int y) override;
|
||||||
void sendMouseButtonEvent(int x, int y, int button, bool pressed) override;
|
void sendMouseButtonEvent(int x, int y, int button, bool pressed) override;
|
||||||
void sendMouseWheelEvent(int x, int y, int deltaX, int deltaY) override;
|
void sendMouseWheelEvent(int x, int y, int deltaX, int deltaY) override;
|
||||||
|
void setClipboardText(const QString& text) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class InputEventType {
|
enum class InputEventType {
|
||||||
@@ -42,6 +43,7 @@ private:
|
|||||||
MouseButton,
|
MouseButton,
|
||||||
MouseWheel,
|
MouseWheel,
|
||||||
Resize,
|
Resize,
|
||||||
|
SetClipboardText,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct InputEvent {
|
struct InputEvent {
|
||||||
@@ -84,6 +86,10 @@ private:
|
|||||||
int m_lastResizeWidth;
|
int m_lastResizeWidth;
|
||||||
int m_lastResizeHeight;
|
int m_lastResizeHeight;
|
||||||
|
|
||||||
|
std::mutex m_cliprdrMutex;
|
||||||
|
void* m_cliprdrContext;
|
||||||
|
QString m_pendingLocalClipboardText;
|
||||||
|
|
||||||
void setState(SessionState state, const QString& message);
|
void setState(SessionState state, const QString& message);
|
||||||
bool validateProfile(QString& message) const;
|
bool validateProfile(QString& message) const;
|
||||||
void startWorker();
|
void startWorker();
|
||||||
@@ -92,12 +98,17 @@ private:
|
|||||||
void enqueueInputEvent(const InputEvent& event);
|
void enqueueInputEvent(const InputEvent& event);
|
||||||
void processInputEvents(rdp_freerdp* instance);
|
void processInputEvents(rdp_freerdp* instance);
|
||||||
bool sendDisplayResize(rdp_freerdp* instance, int width, int height);
|
bool sendDisplayResize(rdp_freerdp* instance, int width, int height);
|
||||||
|
void sendClipboardTextToRemote(rdp_freerdp* instance, const QString& text);
|
||||||
public:
|
public:
|
||||||
void onChannelConnectedEvent(const char* name, void* channelInterface);
|
void onChannelConnectedEvent(const char* name, void* channelInterface);
|
||||||
void onChannelDisconnectedEvent(const char* name, void* channelInterface);
|
void onChannelDisconnectedEvent(const char* name, void* channelInterface);
|
||||||
void onDisplayControlCaps(uint32_t maxNumMonitors,
|
void onDisplayControlCaps(uint32_t maxNumMonitors,
|
||||||
uint32_t maxMonitorAreaFactorA,
|
uint32_t maxMonitorAreaFactorA,
|
||||||
uint32_t maxMonitorAreaFactorB);
|
uint32_t maxMonitorAreaFactorB);
|
||||||
|
void onCliprdrMonitorReady();
|
||||||
|
void onCliprdrServerFormatList(bool hasUnicodeText);
|
||||||
|
void onCliprdrServerFormatDataRequest(uint32_t requestedFormatId);
|
||||||
|
void onCliprdrServerFormatDataResponse(bool success, const uint8_t* data, uint32_t size);
|
||||||
private:
|
private:
|
||||||
void emitStateAsync(SessionState state, const QString& message);
|
void emitStateAsync(SessionState state, const QString& message);
|
||||||
void emitConnectionFailureAsync(const QString& displayMessage, const QString& rawMessage);
|
void emitConnectionFailureAsync(const QString& displayMessage, const QString& rawMessage);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QPoint>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
|
||||||
@@ -46,6 +47,10 @@ public slots:
|
|||||||
virtual void sendInput(const QString& input) = 0;
|
virtual void sendInput(const QString& input) = 0;
|
||||||
virtual void confirmHostKey(bool trustHost) = 0;
|
virtual void confirmHostKey(bool trustHost) = 0;
|
||||||
virtual void updateTerminalSize(int columns, int rows) = 0;
|
virtual void updateTerminalSize(int columns, int rows) = 0;
|
||||||
|
virtual void setClipboardText(const QString& text)
|
||||||
|
{
|
||||||
|
Q_UNUSED(text);
|
||||||
|
}
|
||||||
virtual void sendKeyEvent(int key,
|
virtual void sendKeyEvent(int key,
|
||||||
quint32 nativeScanCode,
|
quint32 nativeScanCode,
|
||||||
const QString& text,
|
const QString& text,
|
||||||
@@ -86,6 +91,10 @@ signals:
|
|||||||
void hostKeyConfirmationRequested(const QString& prompt);
|
void hostKeyConfirmationRequested(const QString& prompt);
|
||||||
void frameUpdated(const QImage& frame);
|
void frameUpdated(const QImage& frame);
|
||||||
void remoteDesktopSizeChanged(int width, int height);
|
void remoteDesktopSizeChanged(int width, int height);
|
||||||
|
void remoteClipboardTextChanged(const QString& text);
|
||||||
|
void cursorImageChanged(const QImage& image, const QPoint& hotspot);
|
||||||
|
void cursorHidden();
|
||||||
|
void cursorReset();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Profile m_profile;
|
Profile m_profile;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include <QPlainTextEdit>
|
#include <QPlainTextEdit>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
|
#include <QMimeData>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
@@ -74,6 +75,8 @@ SessionTab::SessionTab(const Profile& profile,
|
|||||||
m_terminalFontPointSize(preferences.terminalFontPointSize > 0
|
m_terminalFontPointSize(preferences.terminalFontPointSize > 0
|
||||||
? preferences.terminalFontPointSize
|
? preferences.terminalFontPointSize
|
||||||
: 0),
|
: 0),
|
||||||
|
m_clipboardSyncSupported(profile.protocol.compare(QStringLiteral("RDP"), Qt::CaseInsensitive)
|
||||||
|
== 0),
|
||||||
m_sshTerminal(nullptr),
|
m_sshTerminal(nullptr),
|
||||||
m_rdpDisplay(nullptr),
|
m_rdpDisplay(nullptr),
|
||||||
m_terminalOutput(nullptr),
|
m_terminalOutput(nullptr),
|
||||||
@@ -193,6 +196,11 @@ SessionTab::SessionTab(const Profile& profile,
|
|||||||
m_backend,
|
m_backend,
|
||||||
&SessionBackend::sendMouseWheelEvent,
|
&SessionBackend::sendMouseWheelEvent,
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
|
connect(this,
|
||||||
|
&SessionTab::requestSetClipboardText,
|
||||||
|
m_backend,
|
||||||
|
&SessionBackend::setClipboardText,
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
|
||||||
connect(m_backend,
|
connect(m_backend,
|
||||||
&SessionBackend::stateChanged,
|
&SessionBackend::stateChanged,
|
||||||
@@ -237,10 +245,49 @@ SessionTab::SessionTab(const Profile& profile,
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
|
connect(m_backend,
|
||||||
|
&SessionBackend::remoteClipboardTextChanged,
|
||||||
|
this,
|
||||||
|
&SessionTab::onBackendRemoteClipboardTextChanged,
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
connect(m_backend,
|
||||||
|
&SessionBackend::cursorImageChanged,
|
||||||
|
this,
|
||||||
|
[this](const QImage& image, const QPoint& hotspot) {
|
||||||
|
if (m_rdpDisplay != nullptr) {
|
||||||
|
m_rdpDisplay->setCursorImage(image, hotspot);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
connect(m_backend,
|
||||||
|
&SessionBackend::cursorHidden,
|
||||||
|
this,
|
||||||
|
[this]() {
|
||||||
|
if (m_rdpDisplay != nullptr) {
|
||||||
|
m_rdpDisplay->setCursorHidden();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
connect(m_backend,
|
||||||
|
&SessionBackend::cursorReset,
|
||||||
|
this,
|
||||||
|
[this]() {
|
||||||
|
if (m_rdpDisplay != nullptr) {
|
||||||
|
m_rdpDisplay->setCursorDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
|
||||||
m_backendThread->start();
|
m_backendThread->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_clipboardSyncSupported) {
|
||||||
|
connect(QApplication::clipboard(),
|
||||||
|
&QClipboard::dataChanged,
|
||||||
|
this,
|
||||||
|
&SessionTab::onSystemClipboardChanged);
|
||||||
|
}
|
||||||
|
|
||||||
setState(SessionState::Disconnected, QStringLiteral("Ready to connect."));
|
setState(SessionState::Disconnected, QStringLiteral("Ready to connect."));
|
||||||
QTimer::singleShot(0, this, &SessionTab::connectSession);
|
QTimer::singleShot(0, this, &SessionTab::connectSession);
|
||||||
}
|
}
|
||||||
@@ -615,6 +662,36 @@ void SessionTab::onBackendHostKeyConfirmationRequested(const QString& prompt)
|
|||||||
emit requestHostKeyConfirmation(reply == QMessageBox::Yes);
|
emit requestHostKeyConfirmation(reply == QMessageBox::Yes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SessionTab::onBackendRemoteClipboardTextChanged(const QString& text)
|
||||||
|
{
|
||||||
|
if (text == m_lastSyncedClipboardText) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastSyncedClipboardText = text;
|
||||||
|
QApplication::clipboard()->setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionTab::onSystemClipboardChanged()
|
||||||
|
{
|
||||||
|
if (!m_clipboardSyncSupported || m_state != SessionState::Connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QClipboard* clipboard = QApplication::clipboard();
|
||||||
|
if (!clipboard->mimeData()->hasText()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString text = clipboard->text();
|
||||||
|
if (text == m_lastSyncedClipboardText) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastSyncedClipboardText = text;
|
||||||
|
emit requestSetClipboardText(text);
|
||||||
|
}
|
||||||
|
|
||||||
void SessionTab::setupUi()
|
void SessionTab::setupUi()
|
||||||
{
|
{
|
||||||
auto* rootLayout = new QVBoxLayout(this);
|
auto* rootLayout = new QVBoxLayout(this);
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ signals:
|
|||||||
void requestMouseMoveEvent(int x, int y);
|
void requestMouseMoveEvent(int x, int y);
|
||||||
void requestMouseButtonEvent(int x, int y, int button, bool pressed);
|
void requestMouseButtonEvent(int x, int y, int button, bool pressed);
|
||||||
void requestMouseWheelEvent(int x, int y, int deltaX, int deltaY);
|
void requestMouseWheelEvent(int x, int y, int deltaX, int deltaY);
|
||||||
|
void requestSetClipboardText(const QString& text);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onBackendStateChanged(SessionState state, const QString& message);
|
void onBackendStateChanged(SessionState state, const QString& message);
|
||||||
@@ -89,6 +90,8 @@ private slots:
|
|||||||
void onBackendConnectionError(const QString& displayMessage, const QString& rawMessage);
|
void onBackendConnectionError(const QString& displayMessage, const QString& rawMessage);
|
||||||
void onBackendOutputReceived(const QString& text);
|
void onBackendOutputReceived(const QString& text);
|
||||||
void onBackendHostKeyConfirmationRequested(const QString& prompt);
|
void onBackendHostKeyConfirmationRequested(const QString& prompt);
|
||||||
|
void onBackendRemoteClipboardTextChanged(const QString& text);
|
||||||
|
void onSystemClipboardChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Profile m_profile;
|
Profile m_profile;
|
||||||
@@ -100,6 +103,8 @@ private:
|
|||||||
SessionConnectOptions m_lastConnectOptions;
|
SessionConnectOptions m_lastConnectOptions;
|
||||||
QString m_terminalThemeName;
|
QString m_terminalThemeName;
|
||||||
int m_terminalFontPointSize;
|
int m_terminalFontPointSize;
|
||||||
|
QString m_lastSyncedClipboardText;
|
||||||
|
bool m_clipboardSyncSupported;
|
||||||
|
|
||||||
KodoTerm* m_sshTerminal;
|
KodoTerm* m_sshTerminal;
|
||||||
RdpDisplayWidget* m_rdpDisplay;
|
RdpDisplayWidget* m_rdpDisplay;
|
||||||
|
|||||||
Reference in New Issue
Block a user