Commit f37580d7 by Ludmány Balázs

Initial commit

parents
Pipeline #120 skipped in 0 seconds
TEMPLATE = app
QT += qml quick
CONFIG += c++11
SOURCES += main.cpp \
jpegdecoder.cpp \
qvnc.cpp \
uploader.cpp \
dispatcher.cpp \
vncrenderer.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
# Default rules for deployment.
include(deployment.pri)
unix|win32: LIBS += -L$$PWD/../libvncserver/libvncclient/.libs/ -lvncclient
INCLUDEPATH += $$PWD/../libvncserver
DEPENDPATH += $$PWD/../libvncserver
HEADERS += \
vncrenderer.h \
jpegdecoder.h \
qvnc.h \
uploader.h \
dispatcher.h
DISTFILES += \
draw_shader.fsh
unix:!android {
isEmpty(target.path) {
qnx {
target.path = /tmp/$${TARGET}/bin
} else {
target.path = /opt/$${TARGET}/bin
}
export(target.path)
}
INSTALLS += target
}
export(INSTALLS)
#include "dispatcher.h"
rfbBool Dispatcher::MallocFrameBuffer(rfbClient *client)
{
Dispatcher *dispatcher = (Dispatcher *) rfbClientGetClientData(client, 0);
emit dispatcher->resizeFramebuffer(QSize(client->width, client->height));
return TRUE;
}
void Dispatcher::GotCopyRect(rfbClient *client, int src_x, int src_y, int w, int h, int dest_x, int dest_y)
{
Dispatcher *dispatcher = (Dispatcher *) rfbClientGetClientData(client, 0);
dispatcher->uploader().gotCopyRect(src_x, src_y, w, h, dest_x, dest_y);
}
void Dispatcher::GotFillRect(rfbClient *client, int x, int y, int w, int h, uint32_t colour)
{
Dispatcher *dispatcher = (Dispatcher *) rfbClientGetClientData(client, 0);
int red = (colour >> m_client->format.redShift) & m_client->format.redMax;
int green = (colour >> m_client->format.greenShift) & m_client->format.greenMax;
int blue = (colour >> m_client->format.blueShift) & m_client->format.blueMax;
dispatcher->uploader().gotFillRect(x, y, w, h, red, green, blue);
}
void Dispatcher::GotBitmap(rfbClient *client, const uint8_t *buffer, int x, int y, int w, int h)
{
QImage img;
Dispatcher *dispatcher = (Dispatcher *) rfbClientGetClientData(client, 0);
switch(dispatcher->bitsPerPixel()) {
case 8:
img = QImage(buffer, w, h, QImage::Format_Grayscale8);
break;
case 16:
img = QImage(buffer, w, h, QImage::Format_RGB16);
break;
case 32:
img = QImage(buffer, w, h, QImage::Format_RGB32);
break;
default:
emit dispatcher->error();
return;
}
// We have to make a deep copy because libvnc might free the buffer as soon as this function returns
dispatcher->uploader().gotBitmap(img.copy(), x, y, w, h);
}
rfbBool Dispatcher::GotJpeg(rfbClient *client, const uint8_t *buffer, int length, int x, int y, int w, int h)
{
Dispatcher *dispatcher = (Dispatcher *) rfbClientGetClientData(client, 0);
QByteArray jpeg;
jpeg.setRawData(buffer, length);
JpegDecoder *decoder = new JpegDecoder(jpeg, position, size);
connect(decoder, &JpegDecoder::finished, dispatcher, &Dispatcher::decodingFinished);
QThreadPool::globalInstance()->start(decoder);
dispatcher->incrementDecoderCount();
return TRUE;
}
void Dispatcher::FinishedFrameBufferUpdate(rfbClient *client)
{
Dispatcher *dispatcher = (Dispatcher *) rfbClientGetClientData(client, 0);
dispatcher->setFinished(true);
if(decoderCount() == 0) {
dispatcher->uploader().finishedUpdate();
emit dispatcher->updateFramebuffer();
}
}
unsigned int Dispatcher::decoderCount() const
{
return m_decoderCount;
}
unsigned int Dispatcher::incrementDecoderCount()
{
return ++m_decoderCount;
}
unsigned int Dispatcher::decrementDecoderCount()
{
return --m_decoderCount;
}
int Dispatcher::bitsPerPixel()
{
return m_client->format.bitsPerPixel;
}
Uploader &Dispatcher::uploader() const
{
return m_uploader;
}
void Dispatcher::setFinished(bool finished)
{
m_finished = finished;
}
bool Dispatcher::finished() const
{
return m_finished;
}
void Dispatcher::open(QString host, int port)
{
m_client = rfbGetClient(8, 3, 4);
m_client->canHandleNewFBSize = FALSE;
m_client->serverHost = host.toLocal8Bit().data();
m_client->serverPort = port;
m_client->MallocFrameBuffer = Dispatcher::MallocFrameBuffer;
m_client->GotCopyRect = Dispatcher::GotCopyRect;
m_client->GotFillRect = Dispatcher::GotFillRect;
m_client->GotBitmap = Dispatcher::GotBitmap;
m_client->GotJpeg = Dispatcher::GotJpeg;
m_client->FinishedFrameBufferUpdate = Dispatcher::FinishedFrameBufferUpdate;
rfbInitClient(m_client, NULL, NULL);
rfbClientSetClientData(m_client, 0, this);
}
void Dispatcher::refresh()
{
if(m_client == NULL)
return;
m_finished = false;
int message = WaitForMessage(m_client, 500);
if(message < 0) {
emit error();
return;
} else if (message) {
if(!HandleRFBServerMessage(m_client)) {
emit error();
return;
}
}
}
void Dispatcher::terminate()
{
if(m_client == NULL)
return;
rfbClientCleanup(m_client);
}
void Dispatcher::decodingFinished(const QImage &bitmap, const QPoint &position, const QSize &size)
{
m_uploader.gotBitmap(bitmap, position, size);
if(m_finished && decrementDecoderCount() == 0) {
m_uploader.finishedUpdate();
emit updateFramebuffer();
}
}
#ifndef RFBDISPATCH_H
#define RFBDISPATCH_H
#include <QCoreApplication>
#include <QThreadPool>
#include <QObject>
#include <QPoint>
#include <QSize>
#include <QColor>
#include <rfb/rfbclient.h>
#include "uploader.h"
#include "jpegdecoder.h"
class Dispatcher : public QObject
{
Q_OBJECT
public:
// libvnc hooks
static rfbBool MallocFrameBuffer(rfbClient* client);
static void GotCopyRect(rfbClient* client, int src_x, int src_y, int w, int h, int dest_x, int dest_y);
static void GotFillRect(rfbClient* client, int x, int y, int w, int h, uint32_t colour);
static void GotBitmap(rfbClient* client, const uint8_t* buffer, int x, int y, int w, int h);
static rfbBool GotJpeg(rfbClient* client, const uint8_t* buffer, int length, int x, int y, int w, int h);
static void FinishedFrameBufferUpdate(rfbClient* client);
unsigned int decoderCount() const;
unsigned int incrementDecoderCount();
unsigned int decrementDecoderCount();
int bitsPerPixel();
Uploader &uploader() const;
void setFinished(bool finished);
private:
rfbClient *m_client;
unsigned int m_decoderCount;
bool m_finished;
Uploader m_uploader;
signals:
void resizeFramebuffer(const QSize &size);
void updateFramebuffer();
void error();
public slots:
void open(QString host, int port);
void refresh();
void terminate();
void decodingFinished(const QImage &bitmap, const QPoint &position, const QSize &size);
};
#endif // RFBDISPATCH_H
uniform sampler2D qt_Texture0;
varying highp vec4 qt_TexCoord0;
void main(void)
{
gl_FragColor = texture2D(qt_Texture0, qt_TexCoord0.st);
}
#include "jpegdecoder.h"
JpegDecoder::JpegDecoder(const QByteArray &jpeg, const QPoint &position, const QSize &size) :
m_jpeg(jpeg), m_position(position), m_size(size)
{
}
void JpegDecoder::run()
{
m_image.loadFromData(m_jpeg, "JPG");
emit finished(m_image, m_position, m_size);
}
#ifndef JPEGDECODER_H
#define JPEGDECODER_H
#include <QObject>
#include <QByteArray>
#include <QRunnable>
#include <QPoint>
#include <QSize>
#include <QImage>
/*!
* \brief Loads JPEG files on a separate thread
*/
class JpegDecoder : public QRunnable
{
Q_OBJECT
public:
JpegDecoder(const QByteArray &jpeg, const QPoint &position, const QSize &size);
void run();
signals:
void finished(const QImage &image, const QPoint &position, QSize &size);
private:
QByteArray m_jpeg;
QImage m_image;
QPoint m_position;
QSize m_size;
};
#endif // JPEGDECODER_H
#include <QGuiApplication>
#include <QtQml>
#include <QQmlApplicationEngine>
#include "qvnc.h"
int main(int argc, char *argv[])
{
QGuiApplication::setAttribute(Qt::AA_UseOpenGLES);
QGuiApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
QGuiApplication app(argc, argv);
qmlRegisterType<QVnc>("thinclient", 1, 1, "QVnc");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
import QtQuick 2.6
import QtQuick.Controls 2.0
import thinclient 1.1
ApplicationWindow {
visible: true
width: 640
height: 480
header: TabBar {
width: parent.width
TabButton {
text: qsTr("Open connection")
onClicked: vnc.open()
}
TabButton {
text: qsTr("Terminate connection")
onClicked: vnc.terminate()
}
TabButton {
text: qsTr("Quit application")
}
}
QVnc {
id: vnc
host: "vm.ik.bme.hu"
port: 10495
width: 1024
height: 768
}
}
<RCC>
<qresource prefix="/">
<file>main.qml</file>
</qresource>
</RCC>
#include "qvnc.h"
QVnc::QVnc()
{
// Don't let Qt change the size of the OpenGL framebuffer
setTextureFollowsItemSize(false);
m_dispatcher = new Dispatcher;
m_dispatcher->moveToThread(&m_dispatcher_thread);
connect(&m_dispatcher_thread, &QThread::finished, m_dispatcher, &QObject::deleteLater);
connect(this, &QVnc::open_connection, m_dispatcher, &Dispatcher::open);
connect(this, &QVnc::terminate_connection, m_dispatcher, &Dispatcher::terminate);
connect(m_dispatcher, &Dispatcher::updateFramebuffer, this, &QVnc::update);
QThreadPool::globalInstance()->reserveThread();
m_dispatcher_thread.start();
}
QVnc::~QVnc()
{
m_dispatcher_thread.quit();
m_dispatcher_thread.wait();
}
void QVnc::open()
{
emit open_connection(this->m_host, this->m_port);
}
void QVnc::terminate()
{
emit terminate_connection();
}
void QVnc::setHost(QString host)
{
if (m_host == host)
return;
m_host = host;
emit hostChanged(host);
}
void QVnc::setPort(int port)
{
if (m_port == port)
return;
m_port = port;
emit portChanged(port);
}
QQuickFramebufferObject::Renderer *QVnc::createRenderer() const
{
VncRenderer *renderer = new VncRenderer();
connect(renderer, &VncRenderer::rendered, m_dispatcher, &Dispatcher::refresh);
connect(m_dispatcher &Dispatcher::resizeFramebuffer, renderer, &VncRenderer::invalidateFramebufferObject);
return renderer;
}
QString QVnc::host() const
{
return m_host;
}
int QVnc::port() const
{
return m_port;
}
#ifndef VNC_H
#define VNC_H
#include <QObject>
#include <QQuickFramebufferObject>
#include <QThread>
#include <QOffscreenSurface>
#include "dispatcher.h"
#include "vncrenderer.h"
#include "uploader.h"
/*!
* \brief The public interface of the VNC functionality
*/
class QVnc : public QQuickFramebufferObject
{
Q_OBJECT
Q_PROPERTY(QString host READ host WRITE setHost NOTIFY hostChanged)
Q_PROPERTY(int port READ port WRITE setPort NOTIFY portChanged)
public:
explicit QVnc();
~QVnc();
Renderer *createRenderer() const;
QString host() const;
int port() const;
signals:
void open_connection(QString host, int port);
void terminate_connection();
void hostChanged(QString host);
void portChanged(int port);
public slots:
void open();
void update();
void terminate();
void setHost(QString host);
void setPort(int port);
private:
Dispatcher *m_dispatcher;
QThread m_dispatcher_thread;
QString m_host;
int m_port;
};
#endif // VNC_H
#include "uploader.h"
Uploader::Uploader() : m_fill_buffer(QOpenGLBuffer(QOpenGLBuffer::VertexBuffer))
{
// "applications must ensure that create() is only called on the main (GUI) thread"
m_surface.create();
m_uploader_context.setShareContext(QOpenGLContext::globalShareContext());
m_uploader_context.create();
m_uploader_context.makeCurrent(m_surface);
m_fill_buffer.create();
m_draw_buffer.create();
// A single rectangle takes 20 float values to describe (4 vertices * (2 coordinate + 3 color))
m_fill_data.reserve(100);
m_draw_data.reserve(100);
}
void Uploader::startedUpdate()
{
// TODO: bind the framebuffer as texture
}
void Uploader::gotCopyRect(const int &src_x, const int &src_y, const int &w, const int &h, const int &dest_x, const int &dest_y)
{
// TODO: render parts of the framebuffer to temporary texture
}
void Uploader::gotFillRect(const int x, const int y, const int width, const int height, const float red, const float green, const float blue)
{
// |---|---|---|---|---|
// | X | Y | R | G | B |
// |---|---|---|---|---|
// NOTE: -coordinates are transformed to normalized device coordinates in the vertex shader
// -coordinates and colors are extended to vec4 in the vertex shader
// -OpenGL ES 2.0 does not support integer values as input,
// thus we need to cast to float on the CPU side :(
// bottom left
m_fill_data.append((float) x);
m_fill_data.append((float) (y + height));
m_fill_data.append(red);
m_fill_data.append(green);
m_fill_data.append(blue);
// top left
m_fill_data.append((float) x);
m_fill_data.append((float) y);
m_fill_data.append(red);
m_fill_data.append(green);
m_fill_data.append(blue);
// bottom right
m_fill_data.append((float) (x + width));
m_fill_data.append((float) (y + height));
m_fill_data.append(red);
m_fill_data.append(green);
m_fill_data.append(blue);
// top right
m_fill_data.append((float) (x + width));
m_fill_data.append((float) y);
m_fill_data.append(red);
m_fill_data.append(green);
m_fill_data.append(blue);
}
void Uploader::gotBitmap(const QImage &image, const int x, const int y, const int width, const int height)
{
m_draw_textures.append(new QOpenGLTexture(image, QOpenGLTexture::DontGenerateMipMaps));
// bottom left
m_draw_data.append((float) x);
m_draw_data.append((float) (y + height));
m_draw_data.append(0.0f);
m_draw_data.append(0.0f);
// top left
m_draw_data.append((float) x);
m_draw_data.append((float) y);
m_draw_data.append(0.0f);
m_draw_data.append(1.0f);
// bottom right
m_draw_data.append((float) (x + width));
m_draw_data.append((float) (y + height));
m_draw_data.append(1.0f);
m_draw_data.append(0.0f);
// top right
m_draw_data.append((float) (x + width));
m_draw_data.append((float) y);
m_draw_data.append(1.0f);
m_draw_data.append(1.0f);
}
void Uploader::finishedUpdate()
{
QOpenGLFunctions *fun = QOpenGLContext::currentContext()->functions();
// Upload fill
int fill_size = m_fill_data.size() * sizeof(float);
m_fill_buffer.bind();
m_fill_buffer.allocate(fill_size);
m_fill_buffer.write(0, m_fill_data.constData(), fill_size);
m_fill_buffer.release();
m_fill_data.clear();
// Upload draw
int draw_size = m_draw_data.size() * sizeof(float);
m_draw_buffer.bind();
m_draw_buffer.allocate(draw_size);
m_fill_buffer.write(0, m_fill_data.constData(), fill_size);
m_fill_buffer.release();
m_fill_data.clear();
}
QOpenGLBuffer Uploader::draw_buffer() const
{
return m_draw_buffer;
}
QOpenGLBuffer Uploader::fill_buffer() const
{
return m_fill_buffer;
}
#ifndef UPLOADER_H
#define UPLOADER_H
#include <QVector>
#include <QOffscreenSurface>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>
#include <QThreadPool>
#include <QtAlgorithms>
class Uploader : public QObject
{
public:
Uploader();
QOpenGLBuffer fill_buffer() const;
QOpenGLBuffer draw_buffer() const;
void startedUpdate();
void gotCopyRect(const int src_x, const int src_y, const int width, const int height, const int dest_x, const int dest_y);
void gotFillRect(const int x, const int y, const int width, const int height, const float red, const float green, const float blue);
void gotBitmap(const QImage &image, const int x, const int y, const int width, const int height);
void finishedUpdate();
private:
QOffscreenSurface m_surface;
QOpenGLContext m_uploader_context;
QVector<float> m_fill_data;
QOpenGLBuffer m_fill_buffer;
QVector<float> m_draw_data;
QOpenGLBuffer m_draw_buffer;
QVector<*QOpenGLTexture> m_draw_textures;
uchar m_decoders_running;
};
#endif // UPLOADER_H
#include "vncrenderer.h"
VncRenderer::VncRenderer(QOpenGLBuffer fill_buffer, QOpenGLBuffer draw_buffer, QOpenGLTexture draw_texture) :
m_fill_buffer(fill_buffer), m_draw_buffer(draw_buffer), m_draw_texture(draw_texture)
{
QOpenGLShader fill_vshader(QOpenGLShader::Vertex, &m_fill_program);
fill_vshader.compileSourceCode("uniform highp mat4 ortho;\n"
"attribute highp vec2 position;\n"
"attribute lowp vec3 color;\n"
"varying lowp vec4 varcolor;\n"
"void main(void)\n"
"{\n"
" varcolor = vec4(color, 1.0);\n"
" gl_Position = ortho * vec4(position, 0.0, 1.0);\n"
"}\n");
QOpenGLShader fill_fshader(QOpenGLShader::Fragment, &m_fill_program);
fill_fshader.compileSourceCode("varying lowp vec4 varcolor;\n"
"void main(void)\n"
"{\n"
" gl_FragColor = varcolor;\n"
"}\n");
m_fill_program.addShader(&fill_vshader);
m_fill_program.addShader(&fill_fshader);
m_fill_program.link();
m_fill_program.enableAttributeArray("position");
m_fill_program.enableAttributeArray("color");
QOpenGLShader draw_vshader(QOpenGLShader::Vertex, &m_fill_program);
draw_vshader.compileSourceCode("uniform highp mat2 ortho;\n"
"uniform highp mat4 atlasortho;\n"
"attribute highp vec2 position;\n"
"attribute lowp vec2 texture;\n"
"varying lowp vec2 vartexture;\n"
"void main(void)\n"
"{\n"
" vartexture = atlasortho * texture;\n"
" gl_Position = ortho * vec4(position, 0.0, 1.0);\n"
"}\n");
QOpenGLShader draw_fshader(QOpenGLShader::Fragment, &m_fill_program);
draw_fshader.compileSourceCode("uniform sampler2D atlas;\n"
"varying lowp vec2 vartexture;\n"
"void main(void)\n"
"{\n"
" gl_FragColor = texture2D(atlas, vartexture);\n"
"}\n");
m_draw_program.addShader(&draw_vshader);
m_draw_program.addShader(&draw_fshader);
m_draw_program.link();
m_draw_program.enableAttributeArray("position");
m_draw_program.enableAttributeArray("texture");
}
void VncRenderer::render()
{
QOpenGLFunctions *fun = QOpenGLContext::currentContext()->functions();
// fill rectangles with solid color
m_fill_program.bind();
m_fill_buffer.bind();
for(unsigned int i = 0; i < m_fill_count; i++)
fun->glDrawArrays(GL_TRIANGLES, 0, 4);
m_fill_buffer.release();
m_fill_program.release();
m_draw_program.bind();
m_draw_buffer.bind();
m_draw_texture.bind();
foreach(const Uploader::CopyToAtlas &copy, m_copy_data)
fun->glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA32,);
// draw the vertices of the texture atlas
for(int i = 0; i < m_draw_count; i++)
fun->glDrawArrays(GL_TRIANGLES, 0, 4);
m_draw_texture.release();
m_draw_buffer.release();
m_draw_program.release();
QQuickWindow::resetOpenGLState();
emit rendered();
}
QOpenGLFramebufferObject *VncRenderer::createFramebufferObject(const QSize &size)
{
// Set the orthogonal transformation matrix
m_fill_program.bind();
QMatrix4x4 ortho;
ortho.ortho(QRect(QPoint(0, 0), size));
m_fill_program.setUniformValue("ortho", mat);
m_fill_program.release();
return new QOpenGLFramebufferObject(size);
}
void VncRenderer::synchronize(QQuickFramebufferObject *object)
{
QVnc *vnc = static_cast<QVnc*>(object);
}
#ifndef VNCRENDERER_H
#define VNCRENDERER_H
#include <QObject>
#include <QQuickWindow>
#include <QQuickFramebufferObject>
#include <QOpenGLFramebufferObject>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QMatrix4x4>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>
#include "qvnc.h"
#include "uploader.h"
class VncRenderer : public QQuickFramebufferObject::Renderer
{
Q_OBJECT
public:
VncRenderer(QOpenGLBuffer fill_buffer, QOpenGLBuffer draw_buffer, QOpenGLTexture draw_texture);
protected:
void render() override;
QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) override;
void synchronize(QQuickFramebufferObject *) override;
private:
// for filling a rectangle with a solid color
QOpenGLShaderProgram m_fill_program;
QOpenGLBuffer m_fill_buffer;
unsigned int m_fill_count;
// for copying portions of the framebuffer
QVector<Uploader::CopyToAtlas> m_copy_data;
// for drawing from a texture atlas
QOpenGLShaderProgram m_draw_program;
QOpenGLBuffer m_draw_buffer;
QVector<*QOpenGLTexture> m_draw_texture;
unsigned int m_draw_count;
QMatrix4x4 m_ortho;
signals:
void rendered();
};
#endif // VNCRENDERER_H
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment