From 2cf3696d9f11ae2aa2dba56a0774bea10d59a482 Mon Sep 17 00:00:00 2001 From: Andy Nichols Date: Wed, 15 Jun 2016 15:07:41 +0200 Subject: Support multiple connected clients in the VNC plugin Previously it was only possible for one client to connect at a time. Now it is possible for multiple clients to connect to the VNC server and view and interact with the application. Change-Id: I886583a3abea2955367bf2da490127b041b5c5fb Reviewed-by: Laszlo Agocs --- src/plugins/platforms/vnc/qvncclient.cpp | 694 +++++++++++++++++++++++++++++++ 1 file changed, 694 insertions(+) create mode 100644 src/plugins/platforms/vnc/qvncclient.cpp (limited to 'src/plugins/platforms/vnc/qvncclient.cpp') diff --git a/src/plugins/platforms/vnc/qvncclient.cpp b/src/plugins/platforms/vnc/qvncclient.cpp new file mode 100644 index 0000000000..55bef8c13e --- /dev/null +++ b/src/plugins/platforms/vnc/qvncclient.cpp @@ -0,0 +1,694 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qvncclient.h" +#include "qvnc_p.h" + +#include +#include + +#include +#include + +#ifdef Q_OS_WIN +#include +#else +#include +#endif + +QT_BEGIN_NAMESPACE + +QVncClient::QVncClient(QTcpSocket *clientSocket, QVncServer *server) + : QObject(server) + , m_server(server) + , m_clientSocket(clientSocket) + , m_encoder(nullptr) + , m_msgType(0) + , m_handleMsg(false) + , m_encodingsPending(0) + , m_cutTextPending(0) + , m_supportHextile(false) + , m_wantUpdate(false) + , m_keymod(0) + , m_dirtyCursor(false) + , m_updatePending(false) + , m_protocolVersion(V3_3) +{ + connect(m_clientSocket,SIGNAL(readyRead()),this,SLOT(readClient())); + connect(m_clientSocket,SIGNAL(disconnected()),this,SLOT(discardClient())); + + // send protocol version + const char *proto = "RFB 003.003\n"; + m_clientSocket->write(proto, 12); + m_state = Protocol; +} + +QVncClient::~QVncClient() +{ + delete m_encoder; +} + +QTcpSocket *QVncClient::clientSocket() const +{ + return m_clientSocket; +} + +void QVncClient::setDirty(const QRegion ®ion) +{ + m_dirtyRegion += region; + if (m_state == Connected && + ((m_server->dirtyMap()->numDirty > 0) || m_dirtyCursor)) { + scheduleUpdate(); + } +} + +void QVncClient::convertPixels(char *dst, const char *src, int count) const +{ + const int screendepth = m_server->screen()->depth(); + + // cutoffs +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + if (!m_swapBytes) +#endif + if (m_sameEndian) { + if (screendepth == m_pixelFormat.bitsPerPixel) { // memcpy cutoffs + + switch (screendepth) { + case 32: + memcpy(dst, src, count * sizeof(quint32)); + return; + case 16: + if (m_pixelFormat.redBits == 5 + && m_pixelFormat.greenBits == 6 + && m_pixelFormat.blueBits == 5) + { + memcpy(dst, src, count * sizeof(quint16)); + return; + } + } + } else if (screendepth == 16 && m_pixelFormat.bitsPerPixel == 32) { +#if defined(__i386__) // Currently fails on ARM if dst is not 4 byte aligned + const quint32 *src32 = reinterpret_cast(src); + quint32 *dst32 = reinterpret_cast(dst); + int count32 = count * sizeof(quint16) / sizeof(quint32); + while (count32--) { + const quint32 s = *src32++; + quint32 result1; + quint32 result2; + + // red + result1 = ((s & 0xf8000000) | ((s & 0xe0000000) >> 5)) >> 8; + result2 = ((s & 0x0000f800) | ((s & 0x0000e000) >> 5)) << 8; + + // green + result1 |= ((s & 0x07e00000) | ((s & 0x06000000) >> 6)) >> 11; + result2 |= ((s & 0x000007e0) | ((s & 0x00000600) >> 6)) << 5; + + // blue + result1 |= ((s & 0x001f0000) | ((s & 0x001c0000) >> 5)) >> 13; + result2 |= ((s & 0x0000001f) | ((s & 0x0000001c) >> 5)) << 3; + + *dst32++ = result2; + *dst32++ = result1; + } + if (count & 0x1) { + const quint16 *src16 = reinterpret_cast(src); + *dst32 = qt_conv16ToRgb(src16[count - 1]); + } + return; +#endif + } + } + + const int bytesPerPixel = (m_pixelFormat.bitsPerPixel + 7) / 8; + + for (int i = 0; i < count; ++i) { + int r, g, b; + + switch (screendepth) { + case 8: { + QRgb rgb = m_server->screen()->image()->colorTable()[int(*src)]; + r = qRed(rgb); + g = qGreen(rgb); + b = qBlue(rgb); + src++; + break; + } + case 16: { + quint16 p = *reinterpret_cast(src); +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + if (swapBytes) + p = ((p & 0xff) << 8) | ((p & 0xff00) >> 8); +#endif + r = (p >> 11) & 0x1f; + g = (p >> 5) & 0x3f; + b = p & 0x1f; + r <<= 3; + g <<= 2; + b <<= 3; + src += sizeof(quint16); + break; + } + case 32: { + quint32 p = *reinterpret_cast(src); + r = (p >> 16) & 0xff; + g = (p >> 8) & 0xff; + b = p & 0xff; + src += sizeof(quint32); + break; + } + default: { + r = g = b = 0; + qDebug("QVNCServer: don't support %dbpp display", screendepth); + return; + } + } + +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + if (m_swapBytes) + qSwap(r, b); +#endif + + r >>= (8 - m_pixelFormat.redBits); + g >>= (8 - m_pixelFormat.greenBits); + b >>= (8 - m_pixelFormat.blueBits); + + int pixel = (r << m_pixelFormat.redShift) | + (g << m_pixelFormat.greenShift) | + (b << m_pixelFormat.blueShift); + + if (m_sameEndian || m_pixelFormat.bitsPerPixel == 8) { + memcpy(dst, &pixel, bytesPerPixel); + dst += bytesPerPixel; + continue; + } + + + if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { + switch (m_pixelFormat.bitsPerPixel) { + case 16: + pixel = (((pixel & 0x0000ff00) << 8) | + ((pixel & 0x000000ff) << 24)); + break; + case 32: + pixel = (((pixel & 0xff000000) >> 24) | + ((pixel & 0x00ff0000) >> 8) | + ((pixel & 0x0000ff00) << 8) | + ((pixel & 0x000000ff) << 24)); + break; + default: + qDebug("Cannot handle %d bpp client", m_pixelFormat.bitsPerPixel); + } + } else { // QSysInfo::ByteOrder == QSysInfo::LittleEndian + switch (m_pixelFormat.bitsPerPixel) { + case 16: + pixel = (((pixel & 0xff000000) >> 8) | + ((pixel & 0x00ff0000) << 8)); + break; + case 32: + pixel = (((pixel & 0xff000000) >> 24) | + ((pixel & 0x00ff0000) >> 8) | + ((pixel & 0x0000ff00) << 8) | + ((pixel & 0x000000ff) << 24)); + break; + default: + qDebug("Cannot handle %d bpp client", + m_pixelFormat.bitsPerPixel); + break; + } + } + memcpy(dst, &pixel, bytesPerPixel); + dst += bytesPerPixel; + } +} + +void QVncClient::readClient() +{ + QT_VNC_DEBUG() << "readClient" << m_state; + switch (m_state) { + case Disconnected: + + break; + case Protocol: + if (m_clientSocket->bytesAvailable() >= 12) { + char proto[13]; + m_clientSocket->read(proto, 12); + proto[12] = '\0'; + QT_VNC_DEBUG("Client protocol version %s", proto); + if (!strcmp(proto, "RFB 003.008\n")) { + m_protocolVersion = V3_8; + } else if (!strcmp(proto, "RFB 003.007\n")) { + m_protocolVersion = V3_7; + } else { + m_protocolVersion = V3_3; + } + + if (m_protocolVersion == V3_3) { + // No authentication + quint32 auth = htonl(1); + m_clientSocket->write((char *) &auth, sizeof(auth)); + m_state = Init; + } + } + break; + case Authentication: + + break; + case Init: + if (m_clientSocket->bytesAvailable() >= 1) { + quint8 shared; + m_clientSocket->read((char *) &shared, 1); + + // Server Init msg + QRfbServerInit sim; + QRfbPixelFormat &format = sim.format; + switch (m_server->screen()->depth()) { + case 32: + format.bitsPerPixel = 32; + format.depth = 32; + format.bigEndian = 0; + format.trueColor = true; + format.redBits = 8; + format.greenBits = 8; + format.blueBits = 8; + format.redShift = 16; + format.greenShift = 8; + format.blueShift = 0; + break; + + case 24: + format.bitsPerPixel = 24; + format.depth = 24; + format.bigEndian = 0; + format.trueColor = true; + format.redBits = 8; + format.greenBits = 8; + format.blueBits = 8; + format.redShift = 16; + format.greenShift = 8; + format.blueShift = 0; + break; + + case 18: + format.bitsPerPixel = 24; + format.depth = 18; + format.bigEndian = 0; + format.trueColor = true; + format.redBits = 6; + format.greenBits = 6; + format.blueBits = 6; + format.redShift = 12; + format.greenShift = 6; + format.blueShift = 0; + break; + + case 16: + format.bitsPerPixel = 16; + format.depth = 16; + format.bigEndian = 0; + format.trueColor = true; + format.redBits = 5; + format.greenBits = 6; + format.blueBits = 5; + format.redShift = 11; + format.greenShift = 5; + format.blueShift = 0; + break; + + case 15: + format.bitsPerPixel = 16; + format.depth = 15; + format.bigEndian = 0; + format.trueColor = true; + format.redBits = 5; + format.greenBits = 5; + format.blueBits = 5; + format.redShift = 10; + format.greenShift = 5; + format.blueShift = 0; + break; + + case 12: + format.bitsPerPixel = 16; + format.depth = 12; + format.bigEndian = 0; + format.trueColor = true; + format.redBits = 4; + format.greenBits = 4; + format.blueBits = 4; + format.redShift = 8; + format.greenShift = 4; + format.blueShift = 0; + break; + + case 8: + case 4: + format.bitsPerPixel = 8; + format.depth = 8; + format.bigEndian = 0; + format.trueColor = false; + format.redBits = 0; + format.greenBits = 0; + format.blueBits = 0; + format.redShift = 0; + format.greenShift = 0; + format.blueShift = 0; + break; + + default: + qDebug("QVNC cannot drive depth %d", m_server->screen()->depth()); + discardClient(); + return; + } + sim.width = m_server->screen()->geometry().width(); + sim.height = m_server->screen()->geometry().height(); + sim.setName("Qt for Embedded Linux VNC Server"); + sim.write(m_clientSocket); + m_state = Connected; + } + break; + + case Connected: + do { + if (!m_handleMsg) { + m_clientSocket->read((char *)&m_msgType, 1); + m_handleMsg = true; + } + if (m_handleMsg) { + switch (m_msgType ) { + case SetPixelFormat: + setPixelFormat(); + break; + case FixColourMapEntries: + qDebug("Not supported: FixColourMapEntries"); + m_handleMsg = false; + break; + case SetEncodings: + setEncodings(); + break; + case FramebufferUpdateRequest: + frameBufferUpdateRequest(); + break; + case KeyEvent: + keyEvent(); + break; + case PointerEvent: + pointerEvent(); + break; + case ClientCutText: + clientCutText(); + break; + default: + qDebug("Unknown message type: %d", (int)m_msgType); + m_handleMsg = false; + } + } + } while (!m_handleMsg && m_clientSocket->bytesAvailable()); + break; + default: + break; + } +} + +void QVncClient::discardClient() +{ + m_state = Disconnected; + m_server->discardClient(this); +} + +void QVncClient::checkUpdate() +{ + if (!m_wantUpdate) + return; + + if (m_dirtyCursor) { + m_server->screen()->clientCursor->write(this); + m_dirtyCursor = false; + m_wantUpdate = false; + return; + } + + if (!m_dirtyRegion.isEmpty()) { + if (m_encoder) + m_encoder->write(); + m_wantUpdate = false; + m_dirtyRegion = QRegion(); + } +} + +void QVncClient::scheduleUpdate() +{ + if (!m_updatePending) { + m_updatePending = true; + QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); + } +} + +bool QVncClient::event(QEvent *event) +{ + if (event->type() == QEvent::UpdateRequest) { + m_updatePending = false; + checkUpdate(); + return true; + } + return QObject::event(event); +} + +void QVncClient::setPixelFormat() +{ + if (m_clientSocket->bytesAvailable() >= 19) { + char buf[3]; + m_clientSocket->read(buf, 3); // just padding + m_pixelFormat.read(m_clientSocket); + QT_VNC_DEBUG("Want format: %d %d %d %d %d %d %d %d %d %d", + int(m_pixelFormat.bitsPerPixel), + int(m_pixelFormat.depth), + int(m_pixelFormat.bigEndian), + int(m_pixelFormat.trueColor), + int(m_pixelFormat.redBits), + int(m_pixelFormat.greenBits), + int(m_pixelFormat.blueBits), + int(m_pixelFormat.redShift), + int(m_pixelFormat.greenShift), + int(m_pixelFormat.blueShift)); + if (!m_pixelFormat.trueColor) { + qDebug("Can only handle true color clients"); + discardClient(); + } + m_handleMsg = false; + m_sameEndian = (QSysInfo::ByteOrder == QSysInfo::BigEndian) == !!m_pixelFormat.bigEndian; + m_needConversion = pixelConversionNeeded(); +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + m_swapBytes = qvnc_screen->swapBytes(); +#endif + } +} + +void QVncClient::setEncodings() +{ + QRfbSetEncodings enc; + + if (!m_encodingsPending && enc.read(m_clientSocket)) { + m_encodingsPending = enc.count; + if (!m_encodingsPending) + m_handleMsg = false; + } + + if (m_encoder) { + delete m_encoder; + m_encoder = nullptr; + } + + enum Encodings { + Raw = 0, + CopyRect = 1, + RRE = 2, + CoRRE = 4, + Hextile = 5, + ZRLE = 16, + Cursor = -239, + DesktopSize = -223 + }; + + if (m_encodingsPending && (unsigned)m_clientSocket->bytesAvailable() >= + m_encodingsPending * sizeof(quint32)) { + for (int i = 0; i < m_encodingsPending; ++i) { + qint32 enc; + m_clientSocket->read((char *)&enc, sizeof(qint32)); + enc = ntohl(enc); + QT_VNC_DEBUG("QVncServer::setEncodings: %d", enc); + switch (enc) { + case Raw: + if (!m_encoder) { + m_encoder = new QRfbRawEncoder(this); + QT_VNC_DEBUG("QVncServer::setEncodings: using raw"); + } + break; + case CopyRect: + m_supportCopyRect = true; + break; + case RRE: + m_supportRRE = true; + break; + case CoRRE: + m_supportCoRRE = true; + break; + case Hextile: + m_supportHextile = true; + if (m_encoder) + break; + break; + case ZRLE: + m_supportZRLE = true; + break; + case Cursor: + m_supportCursor = true; + qDebug() << "client side cursor supported."; + m_server->screen()->enableClientCursor(this); + break; + case DesktopSize: + m_supportDesktopSize = true; + break; + default: + break; + } + } + m_handleMsg = false; + m_encodingsPending = 0; + } + + if (!m_encoder) { + m_encoder = new QRfbRawEncoder(this); + QT_VNC_DEBUG("QVncServer::setEncodings: fallback using raw"); + } +} + +void QVncClient::frameBufferUpdateRequest() +{ + QT_VNC_DEBUG() << "FramebufferUpdateRequest"; + QRfbFrameBufferUpdateRequest ev; + + if (ev.read(m_clientSocket)) { + if (!ev.incremental) { + QRect r(ev.rect.x, ev.rect.y, ev.rect.w, ev.rect.h); + r.translate(m_server->screen()->geometry().topLeft()); + setDirty(r); + } + m_wantUpdate = true; + checkUpdate(); + m_handleMsg = false; + } +} + +void QVncClient::pointerEvent() +{ + QRfbPointerEvent ev; + if (ev.read(m_clientSocket)) { + const QPoint pos = m_server->screen()->geometry().topLeft() + QPoint(ev.x, ev.y); + QWindowSystemInterface::handleMouseEvent(0, pos, pos, ev.buttons, QGuiApplication::keyboardModifiers()); + m_handleMsg = false; + } +} + +void QVncClient::keyEvent() +{ + QRfbKeyEvent ev; + + if (ev.read(m_clientSocket)) { + if (ev.keycode == Qt::Key_Shift) + m_keymod = ev.down ? m_keymod | Qt::ShiftModifier : + m_keymod & ~Qt::ShiftModifier; + else if (ev.keycode == Qt::Key_Control) + m_keymod = ev.down ? m_keymod | Qt::ControlModifier : + m_keymod & ~Qt::ControlModifier; + else if (ev.keycode == Qt::Key_Alt) + m_keymod = ev.down ? m_keymod | Qt::AltModifier : + m_keymod & ~Qt::AltModifier; + if (ev.unicode || ev.keycode) + QWindowSystemInterface::handleKeyEvent(0, ev.down ? QEvent::KeyPress : QEvent::KeyRelease, ev.keycode, m_keymod, QString(ev.unicode)); + m_handleMsg = false; + } +} + +void QVncClient::clientCutText() +{ + QRfbClientCutText ev; + + if (m_cutTextPending == 0 && ev.read(m_clientSocket)) { + m_cutTextPending = ev.length; + if (!m_cutTextPending) + m_handleMsg = false; + } + + if (m_cutTextPending && m_clientSocket->bytesAvailable() >= m_cutTextPending) { + char *text = new char [m_cutTextPending+1]; + m_clientSocket->read(text, m_cutTextPending); + delete [] text; + m_cutTextPending = 0; + m_handleMsg = false; + } +} + +bool QVncClient::pixelConversionNeeded() const +{ + if (!m_sameEndian) + return true; + +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + if (qvnc_screen->swapBytes()) + return true; +#endif + + const int screendepth = m_server->screen()->depth(); + if (screendepth != m_pixelFormat.bitsPerPixel) + return true; + + switch (screendepth) { + case 32: + case 24: + return false; + case 16: + return (m_pixelFormat.redBits == 5 + && m_pixelFormat.greenBits == 6 + && m_pixelFormat.blueBits == 5); + } + return true; +} + +QT_END_NAMESPACE -- cgit v1.2.3