From b44665c0a28ba3776edebb57548562cf7ddc117f Mon Sep 17 00:00:00 2001 From: Nicolas Morais <45640048+nicmorais@users.noreply.github.com> Date: Wed, 3 Apr 2024 16:06:07 -0300 Subject: [PATCH] examples,build: add Qt client example * CMake: Adds C++ as project language * CMake: Adds WITH_QT option to build Qt client example * Examples: Adds qt5client.cpp * CMake: adds specific way of building Qt client example * CMake: try to find Qt if needed * CMake: makes Qt not required, quiet if not found * CMake: removes CXX as language * CMake: checks if we have C++ compiler before setting standard * CMake: checks C++ compiler before setting project languages I could not find a better solution. It seems like the languages must be set before almost everything. * CMake: check if C++ compiler defined instead of string comparison * CMake: appends CXX to languages, in a better syntax As suggested by dantti * Examples: changes to static_cast in Qt5 client example As suggested by @dantti * Examples: adds description to Qt example * Examples: Qt example can't handle new fb size * Examples: adds additional condition to build Qt example This is need to remove the following message: 'CMake Error: Cannot determine link language for target' * CMake: adds comment about C++ example (Qt client) * Examples: adds CMake remarks to Qt client example * CMake: changes Qt5::Widgets to Qt5Widgets_LIBRARIES Just for standardization --- CMakeLists.txt | 27 +++++- examples/client/qt5client.cpp | 149 ++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 examples/client/qt5client.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index be08005d..9c453d02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,13 @@ cmake_minimum_required(VERSION 3.4) -project(LibVNCServer VERSION 0.9.14 LANGUAGES C) +set(PROJECT_LANGUAGES C) + +if(DEFINED CMAKE_CXX_COMPILER) + set(CMAKE_CXX_STANDARD 17) + list(APPEND PROJECT_LANGUAGES CXX) +endif(DEFINED CMAKE_CXX_COMPILER) + +project(LibVNCServer VERSION 0.9.14 LANGUAGES ${PROJECT_LANGUAGES}) include(CheckFunctionExists) include(CheckSymbolExists) include(CheckIncludeFile) @@ -25,6 +32,7 @@ set(TESTS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/test) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/") set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + if(CMAKE_GENERATOR MATCHES "Unix Makefiles|Ninja") # some LSP servers expect compile_commands.json in the project root add_custom_target( @@ -67,7 +75,7 @@ option(WITH_SASL "Build with SASL support" ON) option(WITH_XCB "Build with XCB support" ON) option(WITH_EXAMPLES "Build examples" ON) option(WITH_TESTS "Build tests" ON) - +option(WITH_QT "Build the Qt client example" ON) if(WITH_ZLIB) @@ -147,6 +155,10 @@ if(WITH_GTK) find_package(GTK2) endif(WITH_GTK) +if(WITH_QT) + find_package(Qt5 COMPONENTS Core Widgets QUIET) +endif(WITH_QT) + if(WITH_LIBSSHTUNNEL) find_path(LIBSSHTUNNEL_INCLUDE_DIR libsshtunnel.h) find_library(LIBSSHTUNNEL_LIBRARY sshtunnel) @@ -656,6 +668,17 @@ if(WITH_EXAMPLES) set_target_properties(client_examples_${e} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/examples/client) target_link_libraries(client_examples_${e} vncclient ${CMAKE_THREAD_LIBS_INIT} ${SDL2_LIBRARY} ${GTK2_LIBRARIES} ${FFMPEG_LIBRARIES} ${LIBSSHTUNNEL_LIBRARY}) endforeach(e ${LIBVNCCLIENT_EXAMPLES}) + + #This example must have its own building instructions, + #apart from the other examples because it is written in + #C++, so it has a distinct file extension and depends on + #a C++ compiler + if(Qt5Widgets_FOUND AND WITH_QT AND DEFINED CMAKE_CXX_COMPILER) + add_executable(client_examples_qt5client ${LIBVNCCLIEXAMPLE_DIR}/qt5client.cpp ${LIBVNCCLIEXAMPLE_DIR}/${qt5client_EXTRA_SOURCES}) + set_target_properties(client_examples_qt5client PROPERTIES OUTPUT_NAME qt5client) + set_target_properties(client_examples_qt5client PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/examples/client) + target_link_libraries(client_examples_qt5client vncclient ${CMAKE_THREAD_LIBS_INIT} ${Qt5Widgets_LIBRARIES}) + endif(Qt5Widgets_FOUND AND WITH_QT AND DEFINED CMAKE_CXX_COMPILER) endif(WITH_EXAMPLES) # diff --git a/examples/client/qt5client.cpp b/examples/client/qt5client.cpp new file mode 100644 index 00000000..6dfaf4f9 --- /dev/null +++ b/examples/client/qt5client.cpp @@ -0,0 +1,149 @@ +/* + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + + /* + * This is an example on how to make a simple VNC client with + * Qt5 Widgets. It suitable for desktop apps, but may not be + * good for mobile. + * It does not implement any form of cryptography, + * authentication support, client-side cursors or framebuffer + * resizing. If you want to make this a part of your + * application, please notice that you may need to change + * the while(true) loop to disconnect the client. + * + * To build this example with all the other components of + * libvncserver, you may need to explicitly define a C++ + * compiler and the path to Qt libs when calling CMake. + * e.g. cmake -DCMAKE_PREFIX_PATH=~/Qt/5.15.2/gcc_64 + * -DCMAKE_CXX_COMPILER=g++ + */ + +#include +#include +#include +#include +#include +#include "rfb/rfbclient.h" +#include + +class VncViewer : public QWidget +{ + //if you want to make use of signals/slots, uncomment the line bellow: + //Q_OBJECT +public: + VncViewer(QWidget *parent = nullptr) {}; + virtual ~VncViewer() {}; + void start(); + std::string serverIp; + int serverPort; + std::thread *vncThread() const; + void paintEvent(QPaintEvent *event) override; + +private: + QImage m_image; + rfbClient *cl; + std::thread *m_vncThread; + static void finishedFramebufferUpdateStatic(rfbClient *cl); + void finishedFramebufferUpdate(rfbClient *cl); +}; + +void VncViewer::finishedFramebufferUpdateStatic(rfbClient *cl) +{ + VncViewer *ptr = static_cast(rfbClientGetClientData(cl, nullptr)); + ptr->finishedFramebufferUpdate(cl); +} + +void VncViewer::finishedFramebufferUpdate(rfbClient *cl) +{ + m_image = QImage(cl->frameBuffer, cl->width, cl->height, QImage::Format_RGB16); + + update(); +} +void VncViewer::paintEvent(QPaintEvent *event) +{ + event->accept(); + + QPainter painter(this); + painter.drawImage(this->rect(), m_image); +} + +void VncViewer::start() +{ + cl = rfbGetClient(8, 3, 4); + cl->format.depth = 24; + cl->format.depth = 16; + cl->format.bitsPerPixel = 16; + cl->format.redShift = 11; + cl->format.greenShift = 5; + cl->format.blueShift = 0; + cl->format.redMax = 0x1f; + cl->format.greenMax = 0x3f; + cl->format.blueMax = 0x1f; + cl->appData.compressLevel = 9; + cl->appData.qualityLevel = 1; + cl->appData.encodingsString = "tight ultra"; + cl->FinishedFrameBufferUpdate = finishedFramebufferUpdateStatic; + cl->serverHost = strdup(serverIp.c_str()); + cl->serverPort = serverPort; + cl->appData.useRemoteCursor = TRUE; + + rfbClientSetClientData(cl, nullptr, this); + + if (rfbInitClient(cl, 0, nullptr)) { + } else { + std::cout << "[INFO] disconnected" << std::endl; + return; + } + + m_vncThread = new std::thread([this]() { + while (true) { + int i = WaitForMessage(cl, 500); + if (i < 0) { + std::cout << "[INFO] disconnected" << std::endl; + rfbClientCleanup(cl); + break; + } + + if (i && !HandleRFBServerMessage(cl)) { + std::cout << "[INFO] disconnected" << std::endl; + rfbClientCleanup(cl); + break; + } + }; + }); +} + +int main(int argc, char *argv[]) +{ + if(argc < 3) { + std::cout << "Usage: " + << argv[0] + << " " + << " " + << "\n"; + return 1; + } + + QApplication a(argc, argv); + VncViewer vncViewer; + vncViewer.serverIp = std::string{argv[1]}; + vncViewer.serverPort = std::atoi(argv[2]); + vncViewer.show(); + vncViewer.start(); + return a.exec(); +}