├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── example ├── CMakeLists.txt └── main.cpp └── src ├── CMakeLists.txt ├── bitmaprectanglesink.h ├── config.h.in ├── cursorchangenotifier.cpp ├── cursorchangenotifier.h ├── freerdpclient.cpp ├── freerdpclient.h ├── freerdpeventloop.cpp ├── freerdpeventloop.h ├── freerdphelpers.cpp ├── freerdphelpers.h ├── global.h ├── letterboxedscreenbuffer.cpp ├── letterboxedscreenbuffer.h ├── pointerchangesink.h ├── rdpqtsoundplugin.cpp ├── rdpqtsoundplugin.h ├── remotedisplaywidget.cpp ├── remotedisplaywidget.h ├── remotedisplaywidget_p.h ├── remotescreenbuffer.cpp ├── remotescreenbuffer.h ├── scaledscreenbuffer.cpp ├── scaledscreenbuffer.h └── screenbuffer.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.user -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | # put binaries to same dir for easier execution 4 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 5 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 6 | 7 | add_subdirectory(src) 8 | add_subdirectory(example) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jolla Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RemoteDisplay 2 | 3 | RemoteDisplay is a RDP client library built upon [Qt](http://qt.digia.com/) and [FreeRDP](http://www.freerdp.com/). 4 | 5 | The library consists of a single widget [RemoteDisplayWidget](src/remotedisplaywidget.h) 6 | which renders the remote display inside it. The widgets sends any mouse movement 7 | or keyboard button presses to the remote host as well as playback any audio the 8 | remote host might playback. 9 | 10 | ## Usage example 11 | ```C++ 12 | #include 13 | #include 14 | #include 15 | 16 | int main(int argc, char *argv[]) { 17 | QApplication a(argc, argv); 18 | 19 | // configuration options 20 | quint16 width = 800; 21 | quint16 height = 600; 22 | QString host = "1.2.3.4"; 23 | quint16 port = 3389; 24 | 25 | // create and show the RemoteDisplay widget 26 | RemoteDisplayWidget w; 27 | w.resize(width, height); 28 | w.setDesktopSize(width, height); 29 | w.connectToHost(host, port); 30 | w.show(); 31 | 32 | // exit program if disconnected 33 | QObject::connect(&w, SIGNAL(disconnected()), &a, SLOT(quit())); 34 | 35 | return a.exec(); 36 | } 37 | ``` 38 | For full example, see the [example subdirectory](example). 39 | 40 | ## Building and installing 41 | In order to use this library you need to build it first. Here's instructions 42 | for Windows and Linux. 43 | 44 | ### Building in Linux 45 | Prerequisites: 46 | * CMake 2.8.10 or newer 47 | * FreeRDP from https://github.com/sailfishos/FreeRDP, branch 'build-fixes' 48 | * Qt 4.8 or newer (5.x not yet supported) 49 | * gcc 4.5 or newer (for some c++11 features) 50 | * Git 51 | 52 | #### Building FreeRDP 53 | First make sure that you have cmake version 2.8.10 or newer installed, this is 54 | required for the FreeRDP to generate cmake config files for finding its libraries. 55 | If you don't have high enough version, then you need to built it yourself, 56 | refer to [install instructions at cmake's site](http://www.cmake.org/cmake/help/install.html). 57 | 58 | To build the FreeRDP, clone its repo first: 59 | ``` 60 | $ git clone https://github.com/sailfishos/FreeRDP.git 61 | $ cd FreeRDP 62 | $ git checkout build-fixes 63 | ``` 64 | Next, cd to its directory and configure the project with cmake: 65 | ``` 66 | $ cmake -DCMAKE_BUILD_TYPE=Release -DWITH_SERVER:BOOL=OFF -DCHANNEL_AUDIN:BOOL=OFF -DCHANNEL_CLIPRDR:BOOL=OFF -DCHANNEL_DISP:BOOL=OFF -DCHANNEL_DRDYNVC:BOOL=OFF -DCHANNEL_DRIVE:BOOL=OFF -DCHANNEL_ECHO:BOOL=OFF -DCHANNEL_PRINTER:BOOL=OFF -DCHANNEL_RAIL:BOOL=OFF -DCHANNEL_RDPEI:BOOL=OFF -DCHANNEL_RDPGFX:BOOL=OFF -DCHANNEL_TSMF:BOOL=OFF . 67 | ``` 68 | There is a lot of features that RemoteDisplay doesn't use currently and so they 69 | can be left off to produce slightly leaner binaries. 70 | Finally build and install FreeRDP: 71 | ``` 72 | $ make 73 | $ sudo make install 74 | ``` 75 | 76 | #### Building RemoteDisplay 77 | First clone the repo: 78 | ``` 79 | $ git clone https://github.com/sailfishos/RemoteDisplay.git 80 | ``` 81 | Next, cd to its directory and configure, build and install it: 82 | ``` 83 | $ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr . 84 | $ make 85 | $ sudo make install 86 | ``` 87 | Note, that if cmake complains of missing qtmultimedia component then sound playback 88 | might not work, but it will not prevent compiling and using of the library. 89 | 90 | Verify that the RemoteDisplay works by starting your RDP server and running 91 | RemoteDisplay's example: 92 | ``` 93 | $ RemoteDisplayExample 1.2.3.4 3389 800 600 94 | ``` 95 | Run RemoteDisplayExample without parameters for explanation what the parameters do. 96 | 97 | ### Building in Windows 98 | Prerequisites: 99 | * CMake 2.8.10 or newer 100 | * FreeRDP from https://github.com/sailfishos/FreeRDP, branch 'build-fixes' 101 | * Qt 4.8 or newer (5.x not yet supported) 102 | * Visual Studio 2010 103 | * Git 104 | 105 | For running commands use **Start > All Programs > Microsoft Visual Studio 2010 > Visual Studio Tools > Visual Studio Command Prompt (2010)**. 106 | 107 | #### Building FreeRDP 108 | First make sure that you have cmake version 2.8.10 or newer installed, this is 109 | required for the FreeRDP to generate cmake config files for finding its libraries. 110 | You can download cmake from [Kitware's CMake download page](http://www.cmake.org/cmake/resources/software.html). 111 | 112 | To build the FreeRDP, clone its repo: 113 | ``` 114 | $ git clone https://github.com/sailfishos/FreeRDP.git 115 | $ cd FreeRDP 116 | $ git checkout build-fixes 117 | ``` 118 | Next, cd to its directory and configure the project with cmake: 119 | ``` 120 | $ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=C:/FreeRDP -G "NMake Makefiles" -DWITH_SERVER:BOOL=OFF -DWITH_OPENSSL:BOOL=OFF -DWITH_WINMM:BOOL=OFF -DCHANNEL_AUDIN:BOOL=OFF -DCHANNEL_CLIPRDR:BOOL=OFF -DCHANNEL_DISP:BOOL=OFF -DCHANNEL_DRDYNVC:BOOL=OFF -DCHANNEL_DRIVE:BOOL=OFF -DCHANNEL_ECHO:BOOL=OFF -DCHANNEL_PRINTER:BOOL=OFF -DCHANNEL_RAIL:BOOL=OFF -DCHANNEL_RDPEI:BOOL=OFF -DCHANNEL_RDPGFX:BOOL=OFF -DCHANNEL_TSMF:BOOL=OFF . 121 | ``` 122 | There is a lot of features that RemoteDisplay doesn't use currently and so they 123 | can be left off to produce slightly leaner binaries. 124 | 125 | Finally build and install FreeRDP: 126 | ``` 127 | $ nmake 128 | $ nmake install 129 | ``` 130 | 131 | #### Building RemoteDisplay 132 | First clone the repo: 133 | ``` 134 | $ git clone https://github.com/sailfishos/RemoteDisplay.git 135 | ``` 136 | Next, cd to its directory and configure, build and install it: 137 | ``` 138 | $ cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=C:\RemoteDisplay -DWinPR_DIR=C:\FreeRDP\cmake\WinPR -DFreeRDP_DIR=C:\FreeRDP\cmake\FreeRDP . 139 | $ nmake 140 | $ nmake install 141 | ``` 142 | After installation verify that the RemoteDisplay works by starting your RDP 143 | server and running RemoteDisplay's example: 144 | ``` 145 | $ cd C:\RemoteDisplay\bin 146 | $ RemoteDisplayExample 1.2.3.4 3389 800 600 147 | ``` 148 | The example likely complains of missing dll files, in which case you can either 149 | copy them to the bin directory or alternatively add their original locations 150 | to PATH before executing the example: 151 | ``` 152 | set PATH=C:\\bin;C:\FreeRDP\release;%PATH% 153 | ``` 154 | Run RemoteDisplayExample without parameters for explanation what the parameters do. 155 | 156 | ## License 157 | Licensed under the ([MIT license](LICENSE)). 158 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(RemoteDisplayExample) 2 | cmake_minimum_required(VERSION 2.8) 3 | 4 | if(CMAKE_COMPILER_IS_GNUCXX) 5 | add_definitions("-std=gnu++0x") 6 | endif(CMAKE_COMPILER_IS_GNUCXX) 7 | 8 | set(CMAKE_AUTOMOC TRUE) 9 | find_package(Qt4 REQUIRED QtCore QtGui) 10 | include(${QT_USE_FILE}) 11 | 12 | aux_source_directory(. SRC_LIST) 13 | add_executable(${PROJECT_NAME} ${SRC_LIST}) 14 | 15 | target_link_libraries(${PROJECT_NAME} RemoteDisplay ${QT_LIBRARIES}) 16 | include_directories(${RemoteDisplay_SOURCE_DIR}) 17 | 18 | install(TARGETS ${PROJECT_NAME} 19 | RUNTIME DESTINATION bin 20 | ) 21 | -------------------------------------------------------------------------------- /example/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | QApplication a(argc, argv); 7 | 8 | RemoteDisplayWidget w; 9 | 10 | auto args = a.arguments(); 11 | if (args.count() < 5) { 12 | qCritical("Usage: RemoteDisplayExample "); 13 | return -1; 14 | } 15 | 16 | auto host = args.at(1); 17 | auto port = args.at(2).toInt(); 18 | auto width = args.at(3).toInt(); 19 | auto height = args.at(4).toInt(); 20 | 21 | w.resize(width, height); 22 | w.setDesktopSize(width, height); 23 | w.connectToHost(host, port); 24 | w.show(); 25 | 26 | QObject::connect(&w, SIGNAL(disconnected()), &a, SLOT(quit())); 27 | 28 | return a.exec(); 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(RemoteDisplay) 2 | cmake_minimum_required(VERSION 2.8) 3 | 4 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) 5 | 6 | if(CMAKE_COMPILER_IS_GNUCXX) 7 | add_definitions("-std=gnu++0x") 8 | endif(CMAKE_COMPILER_IS_GNUCXX) 9 | 10 | set(CMAKE_AUTOMOC TRUE) 11 | find_package(Qt4 OPTIONAL_COMPONENTS QtCore QtGui QtMultimedia) 12 | find_package(WinPR) 13 | find_package(FreeRDP) 14 | 15 | if(QT_QTMULTIMEDIA_FOUND) 16 | set(WITH_QTSOUND 1) 17 | endif(QT_QTMULTIMEDIA_FOUND) 18 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) 19 | 20 | include(${QT_USE_FILE}) 21 | include_directories(${FreeRDP_INCLUDE_DIR} ${WinPR_INCLUDE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 22 | 23 | aux_source_directory(. SRC_LIST) 24 | list(APPEND SRC_LIST 25 | global.h 26 | remotedisplaywidget_p.h 27 | freerdphelpers.h 28 | screenbuffer.h 29 | bitmaprectanglesink.h 30 | pointerchangesink.h 31 | ) 32 | 33 | add_library(${PROJECT_NAME} SHARED ${SRC_LIST}) 34 | set_property(TARGET ${PROJECT_NAME} PROPERTY COMPILE_DEFINITIONS REMOTEDISPLAY_LIBRARY) 35 | target_link_libraries(${PROJECT_NAME} 36 | ${QT_LIBRARIES} 37 | freerdp-client 38 | freerdp-core 39 | freerdp-codec 40 | freerdp-cache 41 | ) 42 | 43 | install(TARGETS ${PROJECT_NAME} 44 | RUNTIME DESTINATION bin 45 | LIBRARY DESTINATION lib 46 | ARCHIVE DESTINATION lib 47 | ) 48 | install(FILES remotedisplaywidget.h global.h DESTINATION include/RemoteDisplay) 49 | -------------------------------------------------------------------------------- /src/bitmaprectanglesink.h: -------------------------------------------------------------------------------- 1 | #ifndef BITMAPRECTANGLESINK_H 2 | #define BITMAPRECTANGLESINK_H 3 | 4 | class QRect; 5 | class QByteArray; 6 | 7 | /** 8 | * The BitmapRectangleSink interface provides a sink where bitmap rectangle 9 | * updates received from remote host can be fed into. 10 | */ 11 | class BitmapRectangleSink { 12 | public: 13 | /** 14 | * This method adds given bitmap rectangle to the sink. 15 | */ 16 | virtual void addRectangle(const QRect &rect, const QByteArray &data) = 0; 17 | }; 18 | 19 | #endif // BITMAPRECTANGLESINK_H 20 | -------------------------------------------------------------------------------- /src/config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef __CONFIG_H 2 | #define __CONFIG_H 3 | 4 | #cmakedefine WITH_QTSOUND 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /src/cursorchangenotifier.cpp: -------------------------------------------------------------------------------- 1 | #include "cursorchangenotifier.h" 2 | #include "freerdphelpers.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace { 14 | 15 | struct CursorData { 16 | CursorData(const QImage &image, const QImage &mask, int hotX, int hotY) 17 | : image(image), mask(mask), hotX(hotX), hotY(hotY) { 18 | } 19 | 20 | QImage image; 21 | QImage mask; 22 | int hotX; 23 | int hotY; 24 | }; 25 | 26 | struct MyPointer { 27 | rdpPointer pointer; 28 | int index; 29 | }; 30 | 31 | MyPointer* getMyPointer(rdpPointer* pointer) { 32 | return reinterpret_cast(pointer); 33 | } 34 | 35 | } 36 | 37 | class CursorChangeNotifierPrivate { 38 | public: 39 | CursorChangeNotifierPrivate() : cursorDataIndex(0) { 40 | } 41 | 42 | QMap cursorDataMap; 43 | int cursorDataIndex; 44 | QMutex mutex; 45 | }; 46 | 47 | CursorChangeNotifier::CursorChangeNotifier(QObject *parent) 48 | : QObject(parent), d_ptr(new CursorChangeNotifierPrivate) { 49 | } 50 | 51 | CursorChangeNotifier::~CursorChangeNotifier() { 52 | delete d_ptr; 53 | } 54 | 55 | void CursorChangeNotifier::addPointer(rdpPointer* pointer) { 56 | Q_D(CursorChangeNotifier); 57 | int w = pointer->width; 58 | int h = pointer->height; 59 | 60 | // build cursor image 61 | QImage image(w, h, bppToImageFormat(pointer->xorBpp)); 62 | freerdp_image_flip(pointer->xorMaskData, image.bits(), w, h, image.depth()); 63 | 64 | // build cursor's mask image 65 | auto data = new BYTE[pointer->lengthAndMask]; 66 | freerdp_bitmap_flip(pointer->andMaskData, data, (w + 7) / 8, h); 67 | QImage mask(w, h, QImage::Format_Mono); 68 | for(int y = 0; y < h; y++) { 69 | for(int x = 0; x < w; x++) { 70 | mask.setPixel(x, y, freerdp_get_pixel(data, x, y, w, h, 1)); 71 | } 72 | } 73 | delete data; 74 | 75 | QMutexLocker(&d->mutex); 76 | d->cursorDataMap[d->cursorDataIndex] = new CursorData(image, mask, pointer->xPos, pointer->yPos); 77 | getMyPointer(pointer)->index = d->cursorDataIndex; 78 | d->cursorDataIndex++; 79 | } 80 | 81 | void CursorChangeNotifier::removePointer(rdpPointer* pointer) { 82 | Q_D(CursorChangeNotifier); 83 | QMutexLocker(&d->mutex); 84 | delete d->cursorDataMap.take(getMyPointer(pointer)->index); 85 | } 86 | 87 | void CursorChangeNotifier::changePointer(rdpPointer* pointer) { 88 | Q_D(CursorChangeNotifier); 89 | // pass the changed pointer index from RDP thread to GUI thread because 90 | // instances of QCursor should not created outside of GUI thread 91 | int index = getMyPointer(pointer)->index; 92 | QMetaObject::invokeMethod(this, "onPointerChanged", Q_ARG(int, index)); 93 | } 94 | 95 | void CursorChangeNotifier::onPointerChanged(int index) { 96 | Q_D(CursorChangeNotifier); 97 | QMutexLocker(&d->mutex); 98 | if (d->cursorDataMap.contains(index)) { 99 | auto data = d->cursorDataMap[index]; 100 | auto imgPixmap = QPixmap::fromImage(data->image); 101 | auto maskBitmap = QBitmap::fromImage(data->mask); 102 | imgPixmap.setMask(maskBitmap); 103 | 104 | emit cursorChanged(QCursor(imgPixmap, data->hotX, data->hotY)); 105 | } 106 | } 107 | 108 | int CursorChangeNotifier::getPointerStructSize() const { 109 | return sizeof(MyPointer); 110 | } 111 | -------------------------------------------------------------------------------- /src/cursorchangenotifier.h: -------------------------------------------------------------------------------- 1 | #ifndef CURSORCHANGENOTIFIER_H 2 | #define CURSORCHANGENOTIFIER_H 3 | 4 | #include 5 | #include "pointerchangesink.h" 6 | 7 | class QCursor; 8 | class CursorChangeNotifierPrivate; 9 | 10 | /** 11 | * The CursorChangeNotifier class notifies when mouse cursor's style should 12 | * be changed. 13 | * 14 | * RDP server passes the currently shown mouse cursor's style over network when 15 | * ever the style changes. This class receives those updates and emits the 16 | * changed cursor style so that it can be applied in the widget. 17 | */ 18 | class CursorChangeNotifier : public QObject, public PointerChangeSink { 19 | Q_OBJECT 20 | public: 21 | CursorChangeNotifier(QObject *parent = 0); 22 | ~CursorChangeNotifier(); 23 | 24 | /** 25 | * Implemented from PointerChangeSink. 26 | */ 27 | virtual int getPointerStructSize() const; 28 | 29 | /** 30 | * Implemented from PointerChangeSink. 31 | */ 32 | virtual void addPointer(rdpPointer* pointer); 33 | 34 | /** 35 | * Implemented from PointerChangeSink. 36 | */ 37 | virtual void removePointer(rdpPointer* pointer); 38 | 39 | /** 40 | * Implemented from PointerChangeSink. 41 | */ 42 | virtual void changePointer(rdpPointer* pointer); 43 | 44 | signals: 45 | /** 46 | * This signal is emitted when current mouse cursor style changes. 47 | */ 48 | void cursorChanged(const QCursor &cursor); 49 | 50 | private slots: 51 | void onPointerChanged(int index); 52 | 53 | private: 54 | Q_DECLARE_PRIVATE(CursorChangeNotifier) 55 | CursorChangeNotifierPrivate* const d_ptr; 56 | }; 57 | 58 | #endif // CURSORCHANGENOTIFIER_H 59 | -------------------------------------------------------------------------------- /src/freerdpclient.cpp: -------------------------------------------------------------------------------- 1 | #include "freerdpclient.h" 2 | #include "config.h" 3 | #include "freerdpeventloop.h" 4 | #include "freerdphelpers.h" 5 | #include "bitmaprectanglesink.h" 6 | #include "pointerchangesink.h" 7 | #include "rdpqtsoundplugin.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #ifdef Q_OS_UNIX 17 | #include 18 | #endif 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | int FreeRdpClient::instanceCount = 0; 26 | 27 | namespace { 28 | 29 | UINT16 qtMouseButtonToRdpButton(Qt::MouseButton button) { 30 | if (button == Qt::LeftButton) { 31 | return PTR_FLAGS_BUTTON1; 32 | } else if (button == Qt::RightButton) { 33 | return PTR_FLAGS_BUTTON2; 34 | } 35 | return 0; 36 | } 37 | 38 | void* channelAddinLoadHook(LPCSTR pszName, LPSTR pszSubsystem, LPSTR pszType, DWORD dwFlags) { 39 | QString name = pszName; 40 | QString subSystem = pszSubsystem; 41 | 42 | if (name == "rdpsnd" && subSystem == "qt") { 43 | return (void*)RdpQtSoundPlugin::create; 44 | } 45 | return freerdp_channels_load_static_addin_entry(pszName, pszSubsystem, pszType, dwFlags); 46 | } 47 | 48 | } 49 | 50 | BOOL FreeRdpClient::PreConnectCallback(freerdp* instance) { 51 | freerdp_channels_pre_connect(instance->context->channels, instance); 52 | emit getMyContext(instance)->self->aboutToConnect(); 53 | return TRUE; 54 | } 55 | 56 | BOOL FreeRdpClient::PostConnectCallback(freerdp* instance) { 57 | auto context = getMyContext(instance); 58 | auto settings = instance->context->settings; 59 | auto self = context->self; 60 | pointer_cache_register_callbacks(instance->update); 61 | 62 | rdpPointer pointer; 63 | memset(&pointer, 0, sizeof(rdpPointer)); 64 | pointer.size = self->pointerChangeSink->getPointerStructSize(); 65 | pointer.New = PointerNewCallback; 66 | pointer.Free = PointerFreeCallback; 67 | pointer.Set = PointerSetCallback; 68 | pointer.SetNull = NULL; 69 | pointer.SetDefault = NULL; 70 | graphics_register_pointer(context->freeRdpContext.graphics, &pointer); 71 | 72 | #ifdef Q_OS_UNIX 73 | // needed for freerdp_keyboard_get_rdp_scancode_from_x11_keycode() to work 74 | freerdp_keyboard_init(settings->KeyboardLayout); 75 | #endif 76 | 77 | freerdp_channels_post_connect(instance->context->channels, instance); 78 | 79 | emit self->connected(); 80 | 81 | return TRUE; 82 | } 83 | 84 | void FreeRdpClient::PostDisconnectCallback(freerdp* instance) { 85 | emit getMyContext(instance)->self->disconnected(); 86 | } 87 | 88 | int FreeRdpClient::ReceiveChannelDataCallback(freerdp *instance, int channelId, 89 | BYTE *data, int size, int flags, int total_size) { 90 | return freerdp_channels_data(instance, channelId, data, size, flags, total_size); 91 | } 92 | 93 | void FreeRdpClient::PointerNewCallback(rdpContext *context, rdpPointer *pointer) { 94 | getMyContext(context)->self->pointerChangeSink->addPointer(pointer); 95 | } 96 | 97 | void FreeRdpClient::PointerFreeCallback(rdpContext *context, rdpPointer *pointer) { 98 | getMyContext(context)->self->pointerChangeSink->removePointer(pointer); 99 | } 100 | 101 | void FreeRdpClient::PointerSetCallback(rdpContext *context, rdpPointer *pointer) { 102 | getMyContext(context)->self->pointerChangeSink->changePointer(pointer); 103 | } 104 | 105 | void FreeRdpClient::BitmapUpdateCallback(rdpContext *context, BITMAP_UPDATE *updates) { 106 | auto self = getMyContext(context)->self; 107 | auto sink = self->bitmapRectangleSink; 108 | 109 | if (sink) { 110 | for (quint32 i = 0; i < updates->number; i++) { 111 | auto u = &updates->rectangles[i]; 112 | QRect rect(u->destLeft, u->destTop, u->width, u->height); 113 | QByteArray data; 114 | Q_ASSERT(u->bitsPerPixel == 16); 115 | 116 | if (u->compressed) { 117 | data.resize(u->width * u->height * (u->bitsPerPixel / 8)); 118 | if (!bitmap_decompress(u->bitmapDataStream, (BYTE*)data.data(), u->width, u->height, u->bitmapLength, u->bitsPerPixel, u->bitsPerPixel)) { 119 | qWarning() << "Bitmap update decompression failed"; 120 | } 121 | } else { 122 | data = QByteArray((char*)u->bitmapDataStream, u->bitmapLength); 123 | } 124 | 125 | sink->addRectangle(rect, data); 126 | } 127 | emit self->desktopUpdated(); 128 | } 129 | } 130 | 131 | FreeRdpClient::FreeRdpClient(PointerChangeSink *pointerSink) 132 | : freeRdpInstance(nullptr), bitmapRectangleSink(nullptr), 133 | pointerChangeSink(pointerSink) { 134 | 135 | if (instanceCount == 0) { 136 | freerdp_channels_global_init(); 137 | freerdp_register_addin_provider(channelAddinLoadHook, 0); 138 | freerdp_wsa_startup(); 139 | } 140 | instanceCount++; 141 | 142 | loop = new FreeRdpEventLoop(this); 143 | } 144 | 145 | FreeRdpClient::~FreeRdpClient() { 146 | if (freeRdpInstance) { 147 | freerdp_channels_free(freeRdpInstance->context->channels); 148 | freerdp_context_free(freeRdpInstance); 149 | freerdp_free(freeRdpInstance); 150 | freeRdpInstance = nullptr; 151 | } 152 | 153 | instanceCount--; 154 | if (instanceCount == 0) { 155 | freerdp_channels_global_uninit(); 156 | freerdp_wsa_cleanup(); 157 | } 158 | } 159 | 160 | void FreeRdpClient::requestStop() { 161 | loop->quit(); 162 | } 163 | 164 | void FreeRdpClient::sendMouseMoveEvent(const QPoint &pos) { 165 | sendMouseEvent(PTR_FLAGS_MOVE, pos); 166 | } 167 | 168 | void FreeRdpClient::sendMousePressEvent(Qt::MouseButton button, const QPoint &pos) { 169 | auto rdpButton = qtMouseButtonToRdpButton(button); 170 | if (!rdpButton) { 171 | return; 172 | } 173 | sendMouseEvent(rdpButton | PTR_FLAGS_DOWN, pos); 174 | } 175 | 176 | void FreeRdpClient::sendMouseReleaseEvent(Qt::MouseButton button, const QPoint &pos) { 177 | auto rdpButton = qtMouseButtonToRdpButton(button); 178 | if (!rdpButton) { 179 | return; 180 | } 181 | sendMouseEvent(rdpButton, pos); 182 | } 183 | 184 | void FreeRdpClient::sendKeyEvent(QKeyEvent *event) { 185 | if (event->isAutoRepeat()) { 186 | return; 187 | } 188 | 189 | bool down = event->type() == QEvent::KeyPress; 190 | auto code = event->nativeScanCode(); 191 | auto input = freeRdpInstance->input; 192 | 193 | #ifdef Q_OS_UNIX 194 | code = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(code); 195 | #endif 196 | 197 | if (input) { 198 | freerdp_input_send_keyboard_event_ex(input, down, code); 199 | } 200 | } 201 | 202 | void FreeRdpClient::setBitmapRectangleSink(BitmapRectangleSink *sink) { 203 | bitmapRectangleSink = sink; 204 | } 205 | 206 | quint8 FreeRdpClient::getDesktopBpp() const { 207 | if (freeRdpInstance && freeRdpInstance->settings) { 208 | return freeRdpInstance->settings->ColorDepth; 209 | } 210 | return 0; 211 | } 212 | 213 | void FreeRdpClient::run() { 214 | 215 | initFreeRDP(); 216 | 217 | auto context = freeRdpInstance->context; 218 | context->cache = cache_new(freeRdpInstance->settings); 219 | 220 | if (!freerdp_connect(freeRdpInstance)) { 221 | qDebug() << "Failed to connect"; 222 | emit disconnected(); 223 | return; 224 | } 225 | 226 | loop->exec(freeRdpInstance); 227 | 228 | freerdp_channels_close(context->channels, freeRdpInstance); 229 | freerdp_disconnect(freeRdpInstance); 230 | 231 | if (context->cache) { 232 | cache_free(context->cache); 233 | } 234 | } 235 | 236 | void FreeRdpClient::initFreeRDP() { 237 | if (freeRdpInstance) { 238 | return; 239 | } 240 | freeRdpInstance = freerdp_new(); 241 | 242 | freeRdpInstance->ContextSize = sizeof(MyContext); 243 | freeRdpInstance->ContextNew = nullptr; 244 | freeRdpInstance->ContextFree = nullptr; 245 | freeRdpInstance->Authenticate = nullptr; 246 | freeRdpInstance->VerifyCertificate = nullptr; 247 | freeRdpInstance->VerifyChangedCertificate = nullptr; 248 | freeRdpInstance->LogonErrorInfo = nullptr; 249 | freeRdpInstance->ReceiveChannelData = ReceiveChannelDataCallback; 250 | freeRdpInstance->PreConnect = PreConnectCallback; 251 | freeRdpInstance->PostConnect = PostConnectCallback; 252 | freeRdpInstance->PostDisconnect = PostDisconnectCallback; 253 | 254 | freerdp_context_new(freeRdpInstance); 255 | getMyContext(freeRdpInstance)->self = this; 256 | 257 | auto update = freeRdpInstance->update; 258 | update->BitmapUpdate = BitmapUpdateCallback; 259 | 260 | auto settings = freeRdpInstance->context->settings; 261 | settings->EmbeddedWindow = TRUE; 262 | 263 | // add sound support 264 | freeRdpInstance->context->channels = freerdp_channels_new(); 265 | #ifdef WITH_QTSOUND 266 | // use Qt Multimedia based audio output 267 | addStaticChannel(QStringList() << "rdpsnd" << "sys:qt"); 268 | #else 269 | // use what FreeRDP provides 270 | addStaticChannel(QStringList() << "rdpsnd"); 271 | #endif 272 | freerdp_client_load_addins(freeRdpInstance->context->channels, settings); 273 | } 274 | 275 | void FreeRdpClient::sendMouseEvent(UINT16 flags, const QPoint &pos) { 276 | // note that this method is called from another thread, so lots of checking 277 | // is needed, perhaps we would need a mutex as well? 278 | if (freeRdpInstance) { 279 | auto input = freeRdpInstance->input; 280 | if (input && input->MouseEvent) { 281 | input->MouseEvent(input, flags, pos.x(), pos.y()); 282 | } 283 | } 284 | } 285 | 286 | void FreeRdpClient::addStaticChannel(const QStringList &args) { 287 | auto argsArray = new char*[args.size()]; 288 | QList delList; 289 | 290 | for (int i = 0; i < args.size(); i++) { 291 | auto d = args[i].toLocal8Bit(); 292 | delList << (argsArray[i] = _strdup(d.data())); 293 | } 294 | 295 | freerdp_client_add_static_channel(freeRdpInstance->settings, args.size(), argsArray); 296 | 297 | qDeleteAll(delList); 298 | delete[] argsArray; 299 | } 300 | 301 | void FreeRdpClient::setSettingServerHostName(const QString &host) { 302 | initFreeRDP(); 303 | auto hostData = host.toLocal8Bit(); 304 | auto settings = freeRdpInstance->context->settings; 305 | free(settings->ServerHostname); 306 | settings->ServerHostname = _strdup(hostData.data()); 307 | } 308 | 309 | void FreeRdpClient::setSettingServerPort(quint16 port) { 310 | initFreeRDP(); 311 | auto settings = freeRdpInstance->context->settings; 312 | settings->ServerPort = port; 313 | } 314 | 315 | void FreeRdpClient::setSettingDesktopSize(quint16 width, quint16 height) { 316 | initFreeRDP(); 317 | auto settings = freeRdpInstance->settings; 318 | settings->DesktopWidth = width; 319 | settings->DesktopHeight = height; 320 | } 321 | -------------------------------------------------------------------------------- /src/freerdpclient.h: -------------------------------------------------------------------------------- 1 | #ifndef FREERDPCLIENT_H 2 | #define FREERDPCLIENT_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class FreeRdpEventLoop; 9 | class Cursor; 10 | class BitmapRectangleSink; 11 | class PointerChangeSink; 12 | class ScreenBuffer; 13 | 14 | class FreeRdpClient : public QObject { 15 | Q_OBJECT 16 | public: 17 | FreeRdpClient(PointerChangeSink *pointerSink); 18 | ~FreeRdpClient(); 19 | 20 | void setBitmapRectangleSink(BitmapRectangleSink *sink); 21 | 22 | quint8 getDesktopBpp() const; 23 | 24 | void sendMouseMoveEvent(const QPoint &pos); 25 | void sendMousePressEvent(Qt::MouseButton button, const QPoint &pos); 26 | void sendMouseReleaseEvent(Qt::MouseButton button, const QPoint &pos); 27 | void sendKeyEvent(QKeyEvent *event); 28 | 29 | public slots: 30 | void setSettingServerHostName(const QString &host); 31 | void setSettingServerPort(quint16 port); 32 | void setSettingDesktopSize(quint16 width, quint16 height); 33 | 34 | void run(); 35 | void requestStop(); 36 | 37 | signals: 38 | void aboutToConnect(); 39 | void connected(); 40 | void disconnected(); 41 | void desktopUpdated(); 42 | 43 | private: 44 | void initFreeRDP(); 45 | void sendMouseEvent(UINT16 flags, const QPoint &pos); 46 | void addStaticChannel(const QStringList& args); 47 | 48 | static void BitmapUpdateCallback(rdpContext *context, BITMAP_UPDATE *updates); 49 | static BOOL PreConnectCallback(freerdp* instance); 50 | static BOOL PostConnectCallback(freerdp* instance); 51 | static void PostDisconnectCallback(freerdp* instance); 52 | static int ReceiveChannelDataCallback(freerdp* instance, int channelId, 53 | BYTE* data, int size, int flags, int total_size); 54 | 55 | static void PointerNewCallback(rdpContext* context, rdpPointer* pointer); 56 | static void PointerFreeCallback(rdpContext* context, rdpPointer* pointer); 57 | static void PointerSetCallback(rdpContext* context, rdpPointer* pointer); 58 | 59 | freerdp* freeRdpInstance; 60 | BitmapRectangleSink *bitmapRectangleSink; 61 | PointerChangeSink *pointerChangeSink; 62 | QPointer loop; 63 | static int instanceCount; 64 | }; 65 | 66 | #endif // FREERDPCLIENT_H 67 | -------------------------------------------------------------------------------- /src/freerdpeventloop.cpp: -------------------------------------------------------------------------------- 1 | #include "freerdpeventloop.h" 2 | #include 3 | #include 4 | 5 | FreeRdpEventLoop::FreeRdpEventLoop(QObject *parent) : 6 | QObject(parent), freeRdpInstance(nullptr) { 7 | } 8 | 9 | void FreeRdpEventLoop::exec(freerdp *instance) { 10 | freeRdpInstance = instance; 11 | shouldQuit = false; 12 | 13 | while(!shouldQuit) { 14 | if (!handleFds()) { 15 | break; 16 | } 17 | QCoreApplication::processEvents(); 18 | } 19 | } 20 | 21 | void FreeRdpEventLoop::quit() { 22 | shouldQuit = true; 23 | } 24 | 25 | bool FreeRdpEventLoop::handleFds() { 26 | int rcount = 0; 27 | int wcount = 0; 28 | void* rfds[32]; 29 | void* wfds[32]; 30 | 31 | memset(rfds, 0, sizeof(rfds)); 32 | memset(wfds, 0, sizeof(wfds)); 33 | 34 | auto channels = freeRdpInstance->context->channels; 35 | 36 | if (!freerdp_get_fds(freeRdpInstance, rfds, &rcount, wfds, &wcount)) { 37 | fprintf(stderr, "Failed to get FreeRDP file descriptor\n"); 38 | return false; 39 | } 40 | 41 | if (!freerdp_channels_get_fds(channels, freeRdpInstance, rfds, &rcount, wfds, &wcount)) { 42 | fprintf(stderr, "Failed to get channel manager file descriptor\n"); 43 | return false; 44 | } 45 | 46 | if (!waitFds(rfds, rcount, wfds, wcount)) { 47 | return false; 48 | } 49 | 50 | if (!freerdp_check_fds(freeRdpInstance)) { 51 | fprintf(stderr, "Failed to check FreeRDP file descriptor\n"); 52 | return false; 53 | } 54 | 55 | if (!freerdp_channels_check_fds(channels, freeRdpInstance)) { 56 | fprintf(stderr, "Failed to check channel manager file descriptor\n"); 57 | return false; 58 | } 59 | 60 | if (freerdp_shall_disconnect(freeRdpInstance)) { 61 | return false; 62 | } 63 | 64 | return true; 65 | } 66 | 67 | #if defined(Q_OS_WIN) 68 | 69 | bool FreeRdpEventLoop::waitFds(void** rfds, int rcount, void** wfds, int wcount) { 70 | int index; 71 | int fds_count = 0; 72 | HANDLE fds[64]; 73 | 74 | // setup read fds 75 | for (index = 0; index < rcount; index++) { 76 | fds[fds_count++] = rfds[index]; 77 | } 78 | 79 | // setup write fds 80 | for (index = 0; index < wcount; index++) { 81 | fds[fds_count++] = wfds[index]; 82 | } 83 | 84 | // exit if nothing to do 85 | if (fds_count == 0) { 86 | fprintf(stderr, "wfreerdp_run: fds_count is zero\n"); 87 | return false; 88 | } 89 | 90 | // do the wait 91 | if (MsgWaitForMultipleObjects(fds_count, fds, FALSE, 1000, QS_ALLINPUT) == WAIT_FAILED) { 92 | fprintf(stderr, "wfreerdp_run: WaitForMultipleObjects failed: 0x%04X\n", GetLastError()); 93 | return false; 94 | } 95 | 96 | return true; 97 | } 98 | 99 | #elif defined(Q_OS_UNIX) 100 | 101 | bool FreeRdpEventLoop::waitFds(void** rfds, int rcount, void** wfds, int wcount) { 102 | int max_fds = 0; 103 | timeval timeout; 104 | fd_set rfds_set; 105 | fd_set wfds_set; 106 | int i; 107 | int fds; 108 | 109 | timeout.tv_sec = 1; 110 | timeout.tv_usec = 0; 111 | 112 | max_fds = 0; 113 | FD_ZERO(&rfds_set); 114 | FD_ZERO(&wfds_set); 115 | 116 | for (i = 0; i < rcount; i++) { 117 | fds = (int)(long)(rfds[i]); 118 | 119 | if (fds > max_fds) { 120 | max_fds = fds; 121 | } 122 | 123 | FD_SET(fds, &rfds_set); 124 | } 125 | 126 | if (max_fds == 0) { 127 | return false; 128 | } 129 | 130 | int select_status = select(max_fds + 1, &rfds_set, NULL, NULL, &timeout); 131 | 132 | if (select_status == 0) { 133 | return true; 134 | } else if (select_status == -1) { 135 | /* these are not really errors */ 136 | if (!((errno == EAGAIN) || (errno == EWOULDBLOCK) || 137 | (errno == EINPROGRESS) || (errno == EINTR))) /* signal occurred */ 138 | { 139 | fprintf(stderr, "xfreerdp_run: select failed\n"); 140 | return false; 141 | } 142 | } 143 | 144 | return true; 145 | } 146 | 147 | #else 148 | #error Implementation missing for current platform! 149 | #endif 150 | -------------------------------------------------------------------------------- /src/freerdpeventloop.h: -------------------------------------------------------------------------------- 1 | #ifndef FREERDPEVENTLOOP_H 2 | #define FREERDPEVENTLOOP_H 3 | 4 | #include 5 | #include 6 | 7 | class FreeRdpEventLoop : public QObject { 8 | Q_OBJECT 9 | public: 10 | FreeRdpEventLoop(QObject *parent = 0); 11 | 12 | void exec(freerdp* instance); 13 | void quit(); 14 | 15 | private: 16 | bool handleFds(); 17 | bool waitFds(void **rfds, int rcount, void **wfds, int wcount); 18 | 19 | freerdp* freeRdpInstance; 20 | bool shouldQuit; 21 | }; 22 | 23 | #endif // FREERDPEVENTLOOP_H 24 | -------------------------------------------------------------------------------- /src/freerdphelpers.cpp: -------------------------------------------------------------------------------- 1 | #include "freerdphelpers.h" 2 | #include 3 | 4 | MyContext::MyContext() : self(nullptr) { 5 | } 6 | 7 | MyContext* getMyContext(rdpContext* context) { 8 | return reinterpret_cast(context); 9 | } 10 | 11 | MyContext* getMyContext(freerdp* instance) { 12 | return getMyContext(instance->context); 13 | } 14 | 15 | QImage::Format bppToImageFormat(int bpp) { 16 | switch (bpp) { 17 | case 16: 18 | return QImage::Format_RGB16; 19 | case 24: 20 | return QImage::Format_RGB888; 21 | case 32: 22 | return QImage::Format_RGB32; 23 | } 24 | qWarning() << "Cannot handle" << bpp << "bits per pixel!"; 25 | return QImage::Format_Invalid; 26 | } 27 | -------------------------------------------------------------------------------- /src/freerdphelpers.h: -------------------------------------------------------------------------------- 1 | #ifndef MYCONTEXT_H 2 | #define MYCONTEXT_H 3 | 4 | class FreeRdpClient; 5 | 6 | #include 7 | #include 8 | 9 | struct MyContext { 10 | MyContext(); 11 | rdpContext freeRdpContext; 12 | FreeRdpClient *self; 13 | }; 14 | 15 | MyContext* getMyContext(rdpContext* context); 16 | MyContext* getMyContext(freerdp* instance); 17 | 18 | QImage::Format bppToImageFormat(int bpp); 19 | 20 | #endif // MYCONTEXT_H 21 | -------------------------------------------------------------------------------- /src/global.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_GLOBAL_H 2 | #define SRC_GLOBAL_H 3 | 4 | #include 5 | 6 | #if defined(REMOTEDISPLAY_LIBRARY) 7 | # define REMOTEDISPLAYSHARED_EXPORT Q_DECL_EXPORT 8 | #else 9 | # define REMOTEDISPLAYSHARED_EXPORT Q_DECL_IMPORT 10 | #endif 11 | 12 | #endif // SRC_GLOBAL_H 13 | -------------------------------------------------------------------------------- /src/letterboxedscreenbuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "letterboxedscreenbuffer.h" 2 | 3 | #include 4 | #include 5 | 6 | class LetterboxedScreenBufferPrivate { 7 | public: 8 | ScreenBuffer *sourceBuffer; 9 | QSize size; 10 | QRect sourceRect; 11 | QTransform coordinateTransform; 12 | }; 13 | 14 | LetterboxedScreenBuffer::LetterboxedScreenBuffer(ScreenBuffer *source, QObject *parent) 15 | : QObject(parent), d_ptr(new LetterboxedScreenBufferPrivate) { 16 | Q_D(LetterboxedScreenBuffer); 17 | d->sourceBuffer = source; 18 | Q_ASSERT(d->sourceBuffer); 19 | } 20 | 21 | LetterboxedScreenBuffer::~LetterboxedScreenBuffer() { 22 | delete d_ptr; 23 | } 24 | 25 | QImage LetterboxedScreenBuffer::createImage() const { 26 | Q_D(const LetterboxedScreenBuffer); 27 | auto sourceImage = d->sourceBuffer->createImage(); 28 | if (!sourceImage.isNull()) { 29 | QImage image(d->size, sourceImage.format()); 30 | QPainter painter(&image); 31 | painter.fillRect(image.rect(), Qt::black); 32 | painter.drawImage(d->sourceRect, sourceImage); 33 | return image; 34 | } 35 | return QImage(); 36 | } 37 | 38 | QPoint LetterboxedScreenBuffer::mapToSource(const QPoint &point) const { 39 | Q_D(const LetterboxedScreenBuffer); 40 | QPoint p = d->coordinateTransform.map(point); 41 | p.setX(qMin(qMax(p.x(), 0), d->sourceRect.width() - 1)); 42 | p.setY(qMin(qMax(p.y(), 0), d->sourceRect.height() - 1)); 43 | return p; 44 | } 45 | 46 | void LetterboxedScreenBuffer::resize(const QSize &size) { 47 | Q_D(LetterboxedScreenBuffer); 48 | d->size = size; 49 | auto sourceImage = d->sourceBuffer->createImage(); 50 | if (!sourceImage.isNull()) { 51 | d->sourceRect.setSize(sourceImage.size()); 52 | d->sourceRect.moveCenter(QPoint(size.width() / 2, size.height() / 2)); 53 | 54 | d->coordinateTransform.reset(); 55 | d->coordinateTransform.translate(-d->sourceRect.left(), -d->sourceRect.top()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/letterboxedscreenbuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef LETTERBOXEDSCREENBUFFER_H 2 | #define LETTERBOXEDSCREENBUFFER_H 3 | 4 | #include 5 | #include "screenbuffer.h" 6 | 7 | class LetterboxedScreenBufferPrivate; 8 | class QPoint; 9 | class QSize; 10 | 11 | /** 12 | * The LetterboxedScreenBuffer class is a wrapper which adds black borders 13 | * around source buffer if necessary. 14 | * 15 | * The class also provides functionality to map coordinates in this buffer 16 | * to coordinates in the source buffer. 17 | */ 18 | class LetterboxedScreenBuffer : public QObject, public ScreenBuffer { 19 | Q_OBJECT 20 | public: 21 | LetterboxedScreenBuffer(ScreenBuffer *source, QObject *parent = 0); 22 | ~LetterboxedScreenBuffer(); 23 | 24 | virtual QImage createImage() const; 25 | 26 | /** 27 | * Maps given @a point in image returned by createImage() to a point in 28 | * the source screen buffer's image. 29 | */ 30 | QPoint mapToSource(const QPoint &point) const; 31 | 32 | /** 33 | * Resizes the screen buffer's dimensions to fit the given @a size. 34 | * Call createImage() to get the image with the new @a size. 35 | */ 36 | void resize(const QSize &size); 37 | 38 | private: 39 | Q_DECLARE_PRIVATE(LetterboxedScreenBuffer) 40 | LetterboxedScreenBufferPrivate* const d_ptr; 41 | }; 42 | 43 | #endif // LETTERBOXEDSCREENBUFFER_H 44 | -------------------------------------------------------------------------------- /src/pointerchangesink.h: -------------------------------------------------------------------------------- 1 | #ifndef POINTERCHANGESINK_H 2 | #define POINTERCHANGESINK_H 3 | 4 | typedef struct rdp_pointer rdpPointer; 5 | 6 | /** 7 | * The PointerChangeSink interface provides a sink where changes to mouse 8 | * cursor/pointer style can be fed into. 9 | */ 10 | class PointerChangeSink { 11 | public: 12 | /** 13 | * Returns size (in bytes) of the struct referenced by rdpPointer that this 14 | * sink expects it to be. 15 | */ 16 | virtual int getPointerStructSize() const = 0; 17 | 18 | /** 19 | * Adds new pointer style to the sink. 20 | */ 21 | virtual void addPointer(rdpPointer* pointer) = 0; 22 | 23 | /** 24 | * Removes previously added pointer style. 25 | */ 26 | virtual void removePointer(rdpPointer* pointer) = 0; 27 | 28 | /** 29 | * Changes current pointer style to given pointer. 30 | */ 31 | virtual void changePointer(rdpPointer* pointer) = 0; 32 | }; 33 | 34 | #endif // POINTERCHANGESINK_H 35 | -------------------------------------------------------------------------------- /src/rdpqtsoundplugin.cpp: -------------------------------------------------------------------------------- 1 | #include "rdpqtsoundplugin.h" 2 | #include "config.h" 3 | #ifdef WITH_QTSOUND 4 | #include 5 | #include 6 | #include 7 | 8 | #define SELF(ARG) ((RdpQtSoundPlugin*)ARG) 9 | 10 | int RdpQtSoundPlugin::create(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) { 11 | auto plugin = new RdpQtSoundPlugin; 12 | 13 | plugin->device.Open = open; 14 | plugin->device.FormatSupported = isFormatSupported; 15 | plugin->device.SetFormat = setFormat; 16 | plugin->device.GetVolume = getVolume; 17 | plugin->device.SetVolume = setVolume; 18 | plugin->device.Play = play; 19 | plugin->device.Start = start; 20 | plugin->device.Close = close; 21 | plugin->device.Free = free; 22 | 23 | pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)plugin); 24 | 25 | return 0; 26 | } 27 | 28 | void RdpQtSoundPlugin::free(rdpsndDevicePlugin *device) { 29 | delete SELF(device); 30 | } 31 | 32 | void RdpQtSoundPlugin::open(rdpsndDevicePlugin *device, AUDIO_FORMAT *format, int latency) { 33 | auto qFormat = SELF(device)->toQtFormat(format); 34 | SELF(device)->resetAudioOut(qFormat, format->nAvgBytesPerSec); 35 | SELF(device)->outDevice = SELF(device)->audioOut->start(); 36 | } 37 | 38 | void RdpQtSoundPlugin::close(rdpsndDevicePlugin *device) { 39 | SELF(device)->audioOut->stop(); 40 | } 41 | 42 | BOOL RdpQtSoundPlugin::isFormatSupported(rdpsndDevicePlugin *device, AUDIO_FORMAT *format) { 43 | return SELF(device)->toQtFormat(format).isValid(); 44 | } 45 | 46 | void RdpQtSoundPlugin::setFormat(rdpsndDevicePlugin *device, AUDIO_FORMAT *format, int latency) { 47 | auto qFormat = SELF(device)->toQtFormat(format); 48 | SELF(device)->resetAudioOut(qFormat, format->nAvgBytesPerSec); 49 | // TODO: What to do with 'latency'? 50 | } 51 | 52 | UINT32 RdpQtSoundPlugin::getVolume(rdpsndDevicePlugin *device) { 53 | // QAudioOutput in Qt5 provides get/set for volume, but not for Qt4 54 | qDebug() << "getVolume(): Not implemented"; 55 | return 0; 56 | } 57 | 58 | void RdpQtSoundPlugin::setVolume(rdpsndDevicePlugin *device, UINT32 value) { 59 | // QAudioOutput in Qt5 provides get/set for volume, but not for Qt4 60 | qDebug() << "setVolume(): Not implemented"; 61 | } 62 | 63 | void RdpQtSoundPlugin::play(rdpsndDevicePlugin *device, BYTE *data, int size) { 64 | if (SELF(device)->outDevice) { 65 | auto wrote = SELF(device)->outDevice->write((char*)data, size); 66 | if (wrote < size) { 67 | qWarning() << "Sound buffer full. Failed to write" << (size - wrote) << "bytes."; 68 | } 69 | } 70 | } 71 | 72 | void RdpQtSoundPlugin::start(rdpsndDevicePlugin *device) { 73 | SELF(device)->audioOut->reset(); 74 | } 75 | 76 | QAudioFormat RdpQtSoundPlugin::toQtFormat(AUDIO_FORMAT *in) const { 77 | if (in == NULL) { 78 | return QAudioFormat(); 79 | } 80 | 81 | QAudioFormat out; 82 | out.setCodec("audio/pcm"); 83 | out.setSampleRate(in->nSamplesPerSec); 84 | out.setChannelCount(in->nChannels); 85 | out.setSampleSize(in->wBitsPerSample); 86 | // TODO: what is correct value for sample type? 87 | out.setSampleType(QAudioFormat::UnSignedInt); 88 | return out; 89 | } 90 | 91 | void RdpQtSoundPlugin::resetAudioOut(const QAudioFormat &format, int avgBytesPerSec) { 92 | delete audioOut; 93 | audioOut = new QAudioOutput(format); 94 | // buffer size worth of 10 seconds should be enough 95 | audioOut->setBufferSize(avgBytesPerSec * 10); 96 | } 97 | 98 | RdpQtSoundPlugin::RdpQtSoundPlugin() { 99 | memset(&device, 0, sizeof(device)); 100 | resetAudioOut(QAudioFormat(), 0); 101 | } 102 | 103 | RdpQtSoundPlugin::~RdpQtSoundPlugin() { 104 | delete audioOut; 105 | } 106 | #else 107 | int RdpQtSoundPlugin::create(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) { 108 | return 0; 109 | } 110 | #endif 111 | -------------------------------------------------------------------------------- /src/rdpqtsoundplugin.h: -------------------------------------------------------------------------------- 1 | #ifndef RDPQTSOUNDPLUGIN_H 2 | #define RDPQTSOUNDPLUGIN_H 3 | 4 | #include 5 | #include 6 | 7 | class QIODevice; 8 | class QAudioOutput; 9 | class QAudioFormat; 10 | 11 | /** 12 | * The RdpQtSoundPlugin class lets FreeRDP to play audio through Qt. 13 | * 14 | * The class implements rdpsnd's plugin interface which will allow plugging it 15 | * into FreeRDP. This class acts as wrapper around QAudioOutput. 16 | */ 17 | class RdpQtSoundPlugin { 18 | public: 19 | static int create(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints); 20 | 21 | // interface "functions" for FreeRDP 22 | static void free(rdpsndDevicePlugin* device); 23 | static void open(rdpsndDevicePlugin* device, AUDIO_FORMAT* format, int latency); 24 | static void close(rdpsndDevicePlugin* device); 25 | static BOOL isFormatSupported(rdpsndDevicePlugin* device, AUDIO_FORMAT* format); 26 | static void setFormat(rdpsndDevicePlugin* device, AUDIO_FORMAT* format, int latency); 27 | static UINT32 getVolume(rdpsndDevicePlugin* device); 28 | static void setVolume(rdpsndDevicePlugin* device, UINT32 value); 29 | static void play(rdpsndDevicePlugin* device, BYTE* data, int size); 30 | static void start(rdpsndDevicePlugin* device); 31 | 32 | private: 33 | QAudioFormat toQtFormat(AUDIO_FORMAT* in) const; 34 | void resetAudioOut(const QAudioFormat &format, int avgBytesPerSec); 35 | 36 | RdpQtSoundPlugin(); 37 | ~RdpQtSoundPlugin(); 38 | 39 | rdpsndDevicePlugin device; 40 | QPointer audioOut; 41 | QPointer outDevice; 42 | }; 43 | 44 | #endif // RDPQTSOUNDPLUGIN_H 45 | -------------------------------------------------------------------------------- /src/remotedisplaywidget.cpp: -------------------------------------------------------------------------------- 1 | #include "remotedisplaywidget.h" 2 | #include "remotedisplaywidget_p.h" 3 | #include "freerdpclient.h" 4 | #include "cursorchangenotifier.h" 5 | #include "remotescreenbuffer.h" 6 | #include "scaledscreenbuffer.h" 7 | #include "letterboxedscreenbuffer.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define FRAMERATE_LIMIT 40 17 | 18 | RemoteDisplayWidgetPrivate::RemoteDisplayWidgetPrivate(RemoteDisplayWidget *q) 19 | : q_ptr(q), repaintNeeded(false) { 20 | processorThread = new QThread(q); 21 | processorThread->start(); 22 | } 23 | 24 | QPoint RemoteDisplayWidgetPrivate::mapToRemoteDesktop(const QPoint &local) const { 25 | QPoint remote; 26 | if (scaledScreenBuffer && letterboxedScreenBuffer) { 27 | remote = scaledScreenBuffer->mapToSource( 28 | letterboxedScreenBuffer->mapToSource(local)); 29 | } 30 | return remote; 31 | } 32 | 33 | void RemoteDisplayWidgetPrivate::resizeScreenBuffers() { 34 | Q_Q(RemoteDisplayWidget); 35 | if (scaledScreenBuffer) { 36 | scaledScreenBuffer->scaleToFit(q->size()); 37 | } 38 | if (letterboxedScreenBuffer) { 39 | letterboxedScreenBuffer->resize(q->size()); 40 | } 41 | } 42 | 43 | void RemoteDisplayWidgetPrivate::onAboutToConnect() { 44 | qDebug() << "ON CONNECT"; 45 | } 46 | 47 | void RemoteDisplayWidgetPrivate::onConnected() { 48 | qDebug() << "ON CONNECTED"; 49 | auto bpp = eventProcessor->getDesktopBpp(); 50 | auto width = desktopSize.width(); 51 | auto height = desktopSize.height(); 52 | 53 | remoteScreenBuffer = new RemoteScreenBuffer(width, height, bpp, this); 54 | scaledScreenBuffer = new ScaledScreenBuffer(remoteScreenBuffer, this); 55 | letterboxedScreenBuffer = new LetterboxedScreenBuffer(scaledScreenBuffer, this); 56 | 57 | eventProcessor->setBitmapRectangleSink(remoteScreenBuffer); 58 | 59 | resizeScreenBuffers(); 60 | } 61 | 62 | void RemoteDisplayWidgetPrivate::onDisconnected() { 63 | Q_Q(RemoteDisplayWidget); 64 | qDebug() << "ON DISCONNECTED"; 65 | emit q->disconnected(); 66 | } 67 | 68 | void RemoteDisplayWidgetPrivate::onCursorChanged(const QCursor &cursor) { 69 | Q_Q(RemoteDisplayWidget); 70 | q->setCursor(cursor); 71 | } 72 | 73 | void RemoteDisplayWidgetPrivate::onDesktopUpdated() { 74 | repaintNeeded = true; 75 | } 76 | 77 | void RemoteDisplayWidgetPrivate::onRepaintTimeout() { 78 | Q_Q(RemoteDisplayWidget); 79 | if (repaintNeeded) { 80 | repaintNeeded = false; 81 | q->repaint(); 82 | } 83 | } 84 | 85 | typedef RemoteDisplayWidgetPrivate Pimpl; 86 | 87 | RemoteDisplayWidget::RemoteDisplayWidget(QWidget *parent) 88 | : QWidget(parent), d_ptr(new RemoteDisplayWidgetPrivate(this)) { 89 | Q_D(RemoteDisplayWidget); 90 | qRegisterMetaType("Qt::MouseButton"); 91 | 92 | setAttribute(Qt::WA_OpaquePaintEvent); 93 | setAttribute(Qt::WA_NoSystemBackground); 94 | setMouseTracking(true); 95 | 96 | auto cursorNotifier = new CursorChangeNotifier(this); 97 | connect(cursorNotifier, SIGNAL(cursorChanged(QCursor)), d, SLOT(onCursorChanged(QCursor))); 98 | 99 | d->eventProcessor = new FreeRdpClient(cursorNotifier); 100 | d->eventProcessor->moveToThread(d->processorThread); 101 | 102 | connect(d->eventProcessor, SIGNAL(aboutToConnect()), d, SLOT(onAboutToConnect())); 103 | connect(d->eventProcessor, SIGNAL(connected()), d, SLOT(onConnected())); 104 | connect(d->eventProcessor, SIGNAL(disconnected()), d, SLOT(onDisconnected())); 105 | connect(d->eventProcessor, SIGNAL(desktopUpdated()), d, SLOT(onDesktopUpdated())); 106 | 107 | auto timer = new QTimer(this); 108 | timer->setSingleShot(false); 109 | timer->setInterval(1000 / FRAMERATE_LIMIT); 110 | connect(timer, SIGNAL(timeout()), d, SLOT(onRepaintTimeout())); 111 | timer->start(); 112 | } 113 | 114 | RemoteDisplayWidget::~RemoteDisplayWidget() { 115 | Q_D(RemoteDisplayWidget); 116 | if (d->eventProcessor) { 117 | QMetaObject::invokeMethod(d->eventProcessor, "requestStop"); 118 | } 119 | d->processorThread->quit(); 120 | d->processorThread->wait(); 121 | 122 | delete d_ptr; 123 | } 124 | 125 | void RemoteDisplayWidget::setDesktopSize(quint16 width, quint16 height) { 126 | Q_D(RemoteDisplayWidget); 127 | d->desktopSize = QSize(width, height); 128 | QMetaObject::invokeMethod(d->eventProcessor, "setSettingDesktopSize", 129 | Q_ARG(quint16, width), Q_ARG(quint16, height)); 130 | } 131 | 132 | void RemoteDisplayWidget::connectToHost(const QString &host, quint16 port) { 133 | Q_D(RemoteDisplayWidget); 134 | 135 | QMetaObject::invokeMethod(d->eventProcessor, "setSettingServerHostName", 136 | Q_ARG(QString, host)); 137 | QMetaObject::invokeMethod(d->eventProcessor, "setSettingServerPort", 138 | Q_ARG(quint16, port)); 139 | 140 | qDebug() << "Connecting to" << host << ":" << port; 141 | QMetaObject::invokeMethod(d->eventProcessor, "run"); 142 | } 143 | 144 | QSize RemoteDisplayWidget::sizeHint() const { 145 | Q_D(const RemoteDisplayWidget); 146 | if (d->desktopSize.isValid()) { 147 | return d->desktopSize; 148 | } 149 | return QWidget::sizeHint(); 150 | } 151 | 152 | void RemoteDisplayWidget::paintEvent(QPaintEvent *event) { 153 | Q_D(RemoteDisplayWidget); 154 | if (d->letterboxedScreenBuffer) { 155 | auto image = d->letterboxedScreenBuffer->createImage(); 156 | if (!image.isNull()) { 157 | QPainter painter(this); 158 | painter.drawImage(rect(), image); 159 | } 160 | } 161 | } 162 | 163 | void RemoteDisplayWidget::mouseMoveEvent(QMouseEvent *event) { 164 | Q_D(RemoteDisplayWidget); 165 | d->eventProcessor->sendMouseMoveEvent(d->mapToRemoteDesktop(event->pos())); 166 | } 167 | 168 | void RemoteDisplayWidget::mousePressEvent(QMouseEvent *event) { 169 | Q_D(RemoteDisplayWidget); 170 | d->eventProcessor->sendMousePressEvent(event->button(), 171 | d->mapToRemoteDesktop(event->pos())); 172 | } 173 | 174 | void RemoteDisplayWidget::mouseReleaseEvent(QMouseEvent *event) { 175 | Q_D(RemoteDisplayWidget); 176 | d->eventProcessor->sendMouseReleaseEvent(event->button(), 177 | d->mapToRemoteDesktop(event->pos())); 178 | } 179 | 180 | void RemoteDisplayWidget::keyPressEvent(QKeyEvent *event) { 181 | Q_D(RemoteDisplayWidget); 182 | d->eventProcessor->sendKeyEvent(event); 183 | event->accept(); 184 | } 185 | 186 | void RemoteDisplayWidget::keyReleaseEvent(QKeyEvent *event) { 187 | Q_D(RemoteDisplayWidget); 188 | d->eventProcessor->sendKeyEvent(event); 189 | event->accept(); 190 | } 191 | 192 | void RemoteDisplayWidget::resizeEvent(QResizeEvent *event) { 193 | Q_D(RemoteDisplayWidget); 194 | d->resizeScreenBuffers(); 195 | QWidget::resizeEvent(event); 196 | } 197 | -------------------------------------------------------------------------------- /src/remotedisplaywidget.h: -------------------------------------------------------------------------------- 1 | #ifndef REMOTEDISPLAYWIDGET_H 2 | #define REMOTEDISPLAYWIDGET_H 3 | 4 | #include 5 | #include "global.h" 6 | 7 | class RemoteDisplayWidgetPrivate; 8 | 9 | class REMOTEDISPLAYSHARED_EXPORT RemoteDisplayWidget : public QWidget { 10 | Q_OBJECT 11 | public: 12 | RemoteDisplayWidget(QWidget *parent = 0); 13 | ~RemoteDisplayWidget(); 14 | 15 | void setDesktopSize(quint16 width, quint16 height); 16 | void connectToHost(const QString &host, quint16 port); 17 | 18 | virtual QSize sizeHint() const; 19 | 20 | signals: 21 | /** 22 | * This signal is emitted when connecting to host fails or if already 23 | * established connection breaks. 24 | */ 25 | void disconnected(); 26 | 27 | protected: 28 | virtual void paintEvent(QPaintEvent *event); 29 | virtual void mouseMoveEvent(QMouseEvent *event); 30 | virtual void mousePressEvent(QMouseEvent *event); 31 | virtual void mouseReleaseEvent(QMouseEvent *event); 32 | virtual void keyPressEvent(QKeyEvent *event); 33 | virtual void keyReleaseEvent(QKeyEvent *event); 34 | virtual void resizeEvent(QResizeEvent *event); 35 | 36 | private: 37 | Q_DECLARE_PRIVATE(RemoteDisplayWidget) 38 | RemoteDisplayWidgetPrivate* const d_ptr; 39 | }; 40 | 41 | #endif // REMOTEDISPLAYWIDGET_H 42 | -------------------------------------------------------------------------------- /src/remotedisplaywidget_p.h: -------------------------------------------------------------------------------- 1 | #ifndef REMOTEDISPLAYWIDGET_P_H 2 | #define REMOTEDISPLAYWIDGET_P_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class RemoteDisplayWidget; 11 | class QThread; 12 | class FreeRdpClient; 13 | class RemoteScreenBuffer; 14 | class ScaledScreenBuffer; 15 | class LetterboxedScreenBuffer; 16 | 17 | class RemoteDisplayWidgetPrivate : public QObject { 18 | Q_OBJECT 19 | public: 20 | RemoteDisplayWidgetPrivate(RemoteDisplayWidget *q); 21 | 22 | QPoint mapToRemoteDesktop(const QPoint &local) const; 23 | void resizeScreenBuffers(); 24 | 25 | QPointer processorThread; 26 | QPointer eventProcessor; 27 | QSize desktopSize; 28 | QRect translatedDesktopRect; 29 | QTransform translatedDesktopMapper; 30 | QPointer remoteScreenBuffer; 31 | QPointer scaledScreenBuffer; 32 | QPointer letterboxedScreenBuffer; 33 | bool repaintNeeded; 34 | 35 | Q_DECLARE_PUBLIC(RemoteDisplayWidget) 36 | RemoteDisplayWidget* const q_ptr; 37 | 38 | private slots: 39 | void onAboutToConnect(); 40 | void onConnected(); 41 | void onDisconnected(); 42 | void onCursorChanged(const QCursor &cursor); 43 | void onDesktopUpdated(); 44 | void onRepaintTimeout(); 45 | }; 46 | 47 | #endif // REMOTEDISPLAYWIDGET_P_H 48 | -------------------------------------------------------------------------------- /src/remotescreenbuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "remotescreenbuffer.h" 2 | #include "freerdphelpers.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class RemoteScreenBufferPrivate { 10 | public: 11 | RemoteScreenBufferPrivate(RemoteScreenBuffer *q) : q_ptr(q) { 12 | } 13 | 14 | void initBuffer(int bpp) { 15 | Q_Q(RemoteScreenBuffer); 16 | Q_ASSERT(isSizeAndFormatValid(bpp)); 17 | if (isSizeAndFormatValid(bpp)) { 18 | bufferData.resize(width * height * bpp); 19 | targetImage = q->createImage(); 20 | targetImage.fill(0); 21 | } 22 | } 23 | 24 | bool isSizeAndFormatValid(int bpp) const { 25 | return width > 0 && height > 0 && bppToImageFormat(bpp) != QImage::Format_Invalid; 26 | } 27 | 28 | QByteArray bufferData; 29 | quint16 width; 30 | quint16 height; 31 | QImage::Format format; 32 | QImage targetImage; 33 | 34 | private: 35 | Q_DECLARE_PUBLIC(RemoteScreenBuffer) 36 | RemoteScreenBuffer* const q_ptr; 37 | }; 38 | 39 | RemoteScreenBuffer::RemoteScreenBuffer(quint16 width, quint16 height, quint8 bpp, QObject *parent) 40 | : QObject(parent), d_ptr(new RemoteScreenBufferPrivate(this)) { 41 | Q_D(RemoteScreenBuffer); 42 | d->width = width; 43 | d->height = height; 44 | d->format = bppToImageFormat(bpp); 45 | d->initBuffer(bpp); 46 | } 47 | 48 | RemoteScreenBuffer::~RemoteScreenBuffer() { 49 | delete d_ptr; 50 | } 51 | 52 | QImage RemoteScreenBuffer::createImage() const { 53 | Q_D(const RemoteScreenBuffer); 54 | return QImage((uchar*)d->bufferData.data(), d->width, d->height, d->format); 55 | } 56 | 57 | void RemoteScreenBuffer::addRectangle(const QRect &rect, const QByteArray &data) { 58 | Q_D(RemoteScreenBuffer); 59 | QImage rectImg((uchar*)data.data(), rect.width(), rect.height(), d->format); 60 | 61 | QPainter painter(&d->targetImage); 62 | painter.drawImage(rect, rectImg); 63 | } 64 | -------------------------------------------------------------------------------- /src/remotescreenbuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef REMOTESCREENBUFFER_H 2 | #define REMOTESCREENBUFFER_H 3 | 4 | #include 5 | #include "screenbuffer.h" 6 | #include "bitmaprectanglesink.h" 7 | 8 | class QImage; 9 | class QRect; 10 | class QByteArray; 11 | class RemoteScreenBufferPrivate; 12 | 13 | /** 14 | * The RemoteScreenBuffer class is a screen buffer which contains the remote 15 | * host's whole display area. 16 | * 17 | * With addRectangle() the RDP handling thread updates the screen buffer. 18 | * 19 | * With createImage() the GUI thread can request for a QImage which provides 20 | * access to the buffer. 21 | */ 22 | class RemoteScreenBuffer : public QObject, public ScreenBuffer, public BitmapRectangleSink { 23 | Q_OBJECT 24 | public: 25 | /** 26 | * Creates new remote screen buffer with dimensions @a width and @a height 27 | * and count of bits per pixel in @a bpp. 28 | */ 29 | RemoteScreenBuffer(quint16 width, quint16 height, quint8 bpp, QObject *parent = 0); 30 | ~RemoteScreenBuffer(); 31 | 32 | virtual QImage createImage() const; 33 | 34 | /** 35 | * Implemented from BitmapRectangleSink. Adds given bitmap rectangle to the 36 | * screen buffer. It is expected that the data is an encoded bitmap which 37 | * can be decompressed with FreeRDP's bitmap_decompress(). 38 | * 39 | * Note that this method is thread-safe. 40 | */ 41 | virtual void addRectangle(const QRect &rect, const QByteArray &data); 42 | 43 | private: 44 | Q_DECLARE_PRIVATE(RemoteScreenBuffer) 45 | RemoteScreenBufferPrivate* const d_ptr; 46 | }; 47 | 48 | #endif // REMOTESCREENBUFFER_H 49 | -------------------------------------------------------------------------------- /src/scaledscreenbuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "scaledscreenbuffer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class ScaledScreenBufferPrivate { 8 | public: 9 | ScreenBuffer *sourceBuffer; 10 | QSize scaledSize; 11 | QTransform coordinateTransform; 12 | }; 13 | 14 | ScaledScreenBuffer::ScaledScreenBuffer(ScreenBuffer *source, QObject *parent) 15 | : QObject(parent), d_ptr(new ScaledScreenBufferPrivate) { 16 | Q_D(ScaledScreenBuffer); 17 | d->sourceBuffer = source; 18 | Q_ASSERT(d->sourceBuffer); 19 | 20 | d->scaledSize = d->sourceBuffer->createImage().size(); 21 | } 22 | 23 | ScaledScreenBuffer::~ScaledScreenBuffer() { 24 | delete d_ptr; 25 | } 26 | 27 | QImage ScaledScreenBuffer::createImage() const { 28 | Q_D(const ScaledScreenBuffer); 29 | auto sourceImage = d->sourceBuffer->createImage(); 30 | if (!sourceImage.isNull()) { 31 | return sourceImage.scaled(d->scaledSize, Qt::IgnoreAspectRatio, 32 | Qt::SmoothTransformation); 33 | } 34 | return QImage(); 35 | } 36 | 37 | void ScaledScreenBuffer::scaleToFit(const QSize &size) { 38 | Q_D(ScaledScreenBuffer); 39 | auto sourceImage = d->sourceBuffer->createImage(); 40 | if (!sourceImage.isNull()) { 41 | QSize sourceSize = sourceImage.size(); 42 | d->scaledSize = sourceSize; 43 | d->scaledSize.scale(size, Qt::KeepAspectRatio); 44 | 45 | qreal scaleX = (qreal)sourceSize.width() / (qreal)d->scaledSize.width(); 46 | qreal scaleY = (qreal)sourceSize.height() / (qreal)d->scaledSize.height(); 47 | d->coordinateTransform.reset(); 48 | d->coordinateTransform.scale(scaleX, scaleY); 49 | } 50 | } 51 | 52 | QPoint ScaledScreenBuffer::mapToSource(const QPoint &point) const { 53 | Q_D(const ScaledScreenBuffer); 54 | return d->coordinateTransform.map(point); 55 | } 56 | -------------------------------------------------------------------------------- /src/scaledscreenbuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef SCALEDSCREENBUFFER_H 2 | #define SCALEDSCREENBUFFER_H 3 | 4 | #include 5 | #include "screenbuffer.h" 6 | 7 | class ScaledScreenBufferPrivate; 8 | class QSize; 9 | class QPoint; 10 | 11 | /** 12 | * The ScaledScreenBuffer class is a wrapper which scales given source screen 13 | * buffer to fit to a given size while keeping its aspect ratio. 14 | * 15 | * The class also provides functionality to map coordinates in the scaled 16 | * buffer to coordinates in the source buffer. 17 | */ 18 | class ScaledScreenBuffer : public QObject, public ScreenBuffer { 19 | Q_OBJECT 20 | public: 21 | ScaledScreenBuffer(ScreenBuffer *source, QObject *parent = 0); 22 | ~ScaledScreenBuffer(); 23 | 24 | virtual QImage createImage() const; 25 | 26 | /** 27 | * Scales the screen buffer's dimensions to fit the given @a size. 28 | * When createImage() is called next time the returned image will be scaled 29 | * by keeping aspect ratio so that it fits within the @a size. 30 | */ 31 | void scaleToFit(const QSize &size); 32 | 33 | /** 34 | * Maps given @a point in image returned by createImage() to a point in 35 | * the source screen buffer's image. 36 | */ 37 | QPoint mapToSource(const QPoint &point) const; 38 | 39 | private: 40 | Q_DECLARE_PRIVATE(ScaledScreenBuffer) 41 | ScaledScreenBufferPrivate* const d_ptr; 42 | }; 43 | 44 | #endif // SCALEDSCREENBUFFER_H 45 | -------------------------------------------------------------------------------- /src/screenbuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef SCREENBUFFER_H 2 | #define SCREENBUFFER_H 3 | 4 | class QImage; 5 | 6 | /** 7 | * Common interface for screen buffer classes. 8 | */ 9 | class ScreenBuffer { 10 | public: 11 | /** 12 | * Creates and returns an image which contains the whole display of 13 | * the screen buffer. 14 | * The returned image can also be null in case of error. 15 | */ 16 | virtual QImage createImage() const = 0; 17 | }; 18 | 19 | #endif // SCREENBUFFER_H 20 | --------------------------------------------------------------------------------