Internal
Public Access
Add VNC remote cursor shape sync
Implements RFB's Cursor pseudo-encoding (RFC 6143 SS7.8.2, type -239): a FramebufferUpdate rectangle carrying a cursor shape instead of screen content (x/y are the hotspot, not position; width/height are the cursor image size), decoded into an ARGB32 QImage using the rectangle's RGB pixel data plus its opacity bitmask, then never painted into the framebuffer. A 0x0 rectangle means "hide the cursor" per spec. VncDisplayWidget gains RdpDisplayWidget's setCursorImage/Hidden/ Default() + applyCursor() shape, reusing its own renderRect()/ effectiveRemoteSize() so cursor scaling works correctly in both the scale-to-fit and actual-size display modes with no special-casing. VNC never emits cursorReset() (RFB's Cursor pseudo-encoding has no "reset to system default" signal, unlike RDP's SetDefault callback) -- setCursorDefault() exists for symmetry but is unused today. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,8 @@ private slots:
|
||||
void unannouncedEncodingFailsConnectionWithClearMessage();
|
||||
void serverCutTextEmitsRemoteClipboardTextChanged();
|
||||
void setClipboardTextSendsClientCutText();
|
||||
void cursorPseudoEncodingProducesExpectedImageAndHotspot();
|
||||
void zeroSizeCursorPseudoEncodingHidesCursor();
|
||||
|
||||
private:
|
||||
std::unique_ptr<FakeVncServer> m_server;
|
||||
@@ -719,5 +721,127 @@ void TestVncSessionBackend::setClipboardTextSendsClientCutText()
|
||||
QTRY_VERIFY(m_server->received.endsWith(expected));
|
||||
}
|
||||
|
||||
void TestVncSessionBackend::cursorPseudoEncodingProducesExpectedImageAndHotspot()
|
||||
{
|
||||
QImage lastCursorImage;
|
||||
QPoint lastHotspot;
|
||||
bool gotCursor = false;
|
||||
connect(m_backend.get(), &SessionBackend::cursorImageChanged, this,
|
||||
[&](const QImage& image, const QPoint& hotspot) {
|
||||
lastCursorImage = image;
|
||||
lastHotspot = hotspot;
|
||||
gotCursor = true;
|
||||
});
|
||||
|
||||
connect(m_server.get(), &FakeVncServer::clientConnected, this, [this]() {
|
||||
m_server->sendWhenConnected(QByteArray("RFB 003.008\n"));
|
||||
});
|
||||
connect(m_server.get(), &FakeVncServer::dataReceived, this, [this]() {
|
||||
switch (m_server->nextStep()) {
|
||||
case 0: {
|
||||
QByteArray securityTypes;
|
||||
securityTypes.append(char(1));
|
||||
securityTypes.append(char(1));
|
||||
m_server->sendWhenConnected(securityTypes);
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
m_server->sendWhenConnected(QByteArray(4, char(0))); // SecurityResult: OK
|
||||
break;
|
||||
case 2: { // ClientInit
|
||||
QByteArray serverInit;
|
||||
serverInit.append(char(0)); serverInit.append(char(4)); // width = 4
|
||||
serverInit.append(char(0)); serverInit.append(char(4)); // height = 4
|
||||
serverInit.append(QByteArray(16, char(0)));
|
||||
serverInit.append(QByteArray(4, char(0)));
|
||||
m_server->sendWhenConnected(serverInit);
|
||||
break;
|
||||
}
|
||||
case 3: { // first FramebufferUpdateRequest -> a Cursor pseudo-encoding rectangle
|
||||
QByteArray update;
|
||||
update.append(char(0)); update.append(char(0));
|
||||
update.append(char(0)); update.append(char(1)); // 1 rectangle
|
||||
update.append(char(0)); update.append(char(1)); // x = hotspot x = 1
|
||||
update.append(char(0)); update.append(char(1)); // y = hotspot y = 1
|
||||
update.append(char(0)); update.append(char(2)); // width = 2
|
||||
update.append(char(0)); update.append(char(1)); // height = 1
|
||||
update.append(char(0xff)); update.append(char(0xff));
|
||||
update.append(char(0xff)); update.append(char(0x11)); // encoding = -239 (Cursor)
|
||||
// pixel 0: red, opaque (byte order B,G,R,pad, per our negotiated format)
|
||||
update.append(char(0)); update.append(char(0)); update.append(char(0xff));
|
||||
update.append(char(0));
|
||||
// pixel 1: blue, will be masked transparent
|
||||
update.append(char(0xff)); update.append(char(0)); update.append(char(0));
|
||||
update.append(char(0));
|
||||
// mask: ceil(2/8)=1 byte/row * height 1 -- bit7=pixel0 opaque, bit6=pixel1 transparent
|
||||
update.append(char(0x80));
|
||||
m_server->sendWhenConnected(update);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
m_backend->connectSession(makeOptions());
|
||||
QTRY_VERIFY(gotCursor);
|
||||
QCOMPARE(lastCursorImage.size(), QSize(2, 1));
|
||||
QCOMPARE(lastHotspot, QPoint(1, 1));
|
||||
QCOMPARE(lastCursorImage.pixelColor(0, 0), QColor(255, 0, 0, 255));
|
||||
QCOMPARE(lastCursorImage.pixelColor(1, 0).alpha(), 0);
|
||||
}
|
||||
|
||||
void TestVncSessionBackend::zeroSizeCursorPseudoEncodingHidesCursor()
|
||||
{
|
||||
bool gotHidden = false;
|
||||
connect(m_backend.get(), &SessionBackend::cursorHidden, this,
|
||||
[&gotHidden]() { gotHidden = true; });
|
||||
|
||||
connect(m_server.get(), &FakeVncServer::clientConnected, this, [this]() {
|
||||
m_server->sendWhenConnected(QByteArray("RFB 003.008\n"));
|
||||
});
|
||||
connect(m_server.get(), &FakeVncServer::dataReceived, this, [this]() {
|
||||
switch (m_server->nextStep()) {
|
||||
case 0: {
|
||||
QByteArray securityTypes;
|
||||
securityTypes.append(char(1));
|
||||
securityTypes.append(char(1));
|
||||
m_server->sendWhenConnected(securityTypes);
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
m_server->sendWhenConnected(QByteArray(4, char(0))); // SecurityResult: OK
|
||||
break;
|
||||
case 2: { // ClientInit
|
||||
QByteArray serverInit;
|
||||
serverInit.append(char(0)); serverInit.append(char(1));
|
||||
serverInit.append(char(0)); serverInit.append(char(1));
|
||||
serverInit.append(QByteArray(16, char(0)));
|
||||
serverInit.append(QByteArray(4, char(0)));
|
||||
m_server->sendWhenConnected(serverInit);
|
||||
break;
|
||||
}
|
||||
case 3: { // a 0x0 Cursor rectangle means "hide the cursor"
|
||||
QByteArray update;
|
||||
update.append(char(0)); update.append(char(0));
|
||||
update.append(char(0)); update.append(char(1)); // 1 rectangle
|
||||
update.append(char(0)); update.append(char(0)); // x (hotspot, unused here)
|
||||
update.append(char(0)); update.append(char(0)); // y
|
||||
update.append(char(0)); update.append(char(0)); // width = 0
|
||||
update.append(char(0)); update.append(char(0)); // height = 0
|
||||
update.append(char(0xff)); update.append(char(0xff));
|
||||
update.append(char(0xff)); update.append(char(0x11)); // encoding = -239 (Cursor)
|
||||
m_server->sendWhenConnected(update);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
m_backend->connectSession(makeOptions());
|
||||
QTRY_VERIFY(gotHidden);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(TestVncSessionBackend)
|
||||
#include "test_vnc_session_backend.moc"
|
||||
|
||||
Reference in New Issue
Block a user