├── .appveyor.yml ├── .clang-format ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CMakeLists-ExternalProjects.txt ├── CMakeLists.txt ├── LICENSE ├── README.md ├── include ├── Logging.h ├── RAT.h └── cxxopts.hpp ├── modules └── FindLIBJPEGTURBO.cmake ├── src ├── ClientDriver.cpp ├── Desktop_Server.ico ├── ServerDriver.cpp ├── client │ ├── .angular-cli.json │ ├── .editorconfig │ ├── .vscode │ │ ├── 0.010801647396913694.bat │ │ └── 0.8898083114717834.bat │ ├── CMakeLists.txt │ ├── dummy.cpp │ ├── main.ts │ ├── package.json │ ├── postcss.config.js │ ├── src │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── app.component.html │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ ├── connect.dialog │ │ │ │ ├── connect.dialog.html │ │ │ │ └── connect.dialog.ts │ │ │ ├── lib │ │ │ │ ├── input_lite.ts │ │ │ │ └── rat_lite.ts │ │ │ ├── material.module.ts │ │ │ ├── models │ │ │ │ └── connect.model.ts │ │ │ ├── monitorcanvas │ │ │ │ ├── monitorcanvas.component.html │ │ │ │ └── monitorcanvas.component.ts │ │ │ ├── options.dialog │ │ │ │ ├── monitorstowatch.component.html │ │ │ │ ├── monitorstowatch.component.ts │ │ │ │ ├── options.dialog.html │ │ │ │ └── options.dialog.ts │ │ │ └── validators │ │ │ │ └── numericonly.ts │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.scss │ │ ├── test.ts │ │ ├── tsconfig.app.json │ │ └── typings.d.ts │ ├── tsconfig.json │ └── webpack.config.js └── server │ ├── CMakeLists.txt │ ├── Server.cpp │ ├── Server.h │ ├── ServerFunctions.cpp │ ├── ServerFunctions.h │ ├── main.cpp │ └── windows │ ├── RAT_Server.rc │ └── resource.h └── test ├── AboutKeyAndCert.txt ├── CMakeLists.txt ├── main.cpp ├── private.key └── public.crt /.appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | 3 | configuration: 4 | - Debug 5 | 6 | clone_folder: c:\projects\proj 7 | 8 | platform: 9 | - x86 10 | 11 | install: 12 | - ps: If ($env:Platform -Match "x86"){ $env:CMAKE_ARCH="" } Else { $env:CMAKE_ARCH=" Win64"} 13 | - mkdir C:\projects\deps 14 | - cd C:\projects\deps 15 | - set CMAKE_URL="https://cmake.org/files/v3.10/cmake-3.10.0-win64-x64.zip" 16 | - appveyor DownloadFile %CMAKE_URL% -FileName cmake.zip 17 | - 7z x cmake.zip -oC:\projects\deps > nul 18 | - move C:\projects\deps\cmake-* C:\projects\deps\cmake # Move to a version-agnostic directory 19 | - set PATH=C:\projects\deps\cmake\bin;%PATH% 20 | - cmake --version 21 | - ps: Install-Product node 8.8.1 22 | - cd c:\projects 23 | - mkdir build 24 | - cd build 25 | - git clone https://github.com/Microsoft/vcpkg 26 | - cd vcpkg 27 | - powershell -exec bypass scripts\bootstrap.ps1 28 | - .\vcpkg integrate install 29 | - .\vcpkg install zlib:x86-windows-static libjpeg-turbo:x86-windows-static openssl:x86-windows-static zlib openssl libjpeg-turbo 30 | - npm install -g typescript electron @angular/cli@latest 31 | 32 | build_script: 33 | - cd c:\projects\proj 34 | - mkdir build 35 | - cd build 36 | - cmake -DBUILD_SHARED_LIBS=OFF -DVCPKG_TARGET_TRIPLET=x86-windows-static "-DCMAKE_TOOLCHAIN_FILE=c:\projects\build\vcpkg\scripts\buildsystems\vcpkg.cmake" .. 37 | - cmake --build . 38 | - ctest -V -C Debug 39 | - cmake -DBUILD_SHARED_LIBS=ON "-DCMAKE_TOOLCHAIN_FILE=c:\projects\build\vcpkg\scripts\buildsystems\vcpkg.cmake" .. 40 | - cmake --build . 41 | - ctest -V -C Debug 42 | 43 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | Language: Cpp 3 | IndentWidth: 4 4 | TabWidth: 8 5 | UseTab: Never 6 | BreakBeforeBraces: Stroustrup 7 | NamespaceIndentation: Inner 8 | ColumnLimit: 150 9 | 10 | Language: JavaScript 11 | IndentWidth: 4 12 | TabWidth: 8 13 | UseTab: Never 14 | ColumnLimit: 150 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | *.opendb 10 | 11 | # Build results 12 | [Dd]ebug/ 13 | [Dd]ebugPublic/ 14 | [Rr]elease/ 15 | [Rr]eleases/ 16 | x64/ 17 | x86/ 18 | build/ 19 | bld/ 20 | libs/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | .workspace/ 24 | .gradle/ 25 | [Jj]ni[Ll]ibs/ 26 | .vs/ 27 | *.js 28 | !postcss.config.js 29 | !webpack.config.js 30 | .codelite/ 31 | .build-*/ 32 | *.tlog 33 | # Roslyn cache directories 34 | *.ide/ 35 | src/client/dist/ 36 | 37 | # MSTest test Results 38 | [Tt]est[Rr]esult*/ 39 | [Bb]uild[Ll]og.* 40 | 41 | #NUNIT 42 | *.VisualState.xml 43 | TestResult.xml 44 | 45 | # Build Results of an ATL Project 46 | [Dd]ebugPS/ 47 | [Rr]eleasePS/ 48 | dlldata.c 49 | 50 | *.so 51 | *.a 52 | *.db 53 | *_i.c 54 | *_p.c 55 | *_i.h 56 | *.ilk 57 | *.meta 58 | *.obj 59 | *.pch 60 | *.pdb 61 | *.pgc 62 | *.pgd 63 | *.rsp 64 | *.sbr 65 | *.tlb 66 | *.tli 67 | *.tlh 68 | *.tmp 69 | *.tmp_proj 70 | *.log 71 | *.vspscc 72 | *.vssscc 73 | .builds 74 | *.pidb 75 | *.svclog 76 | *.scc 77 | *.mk 78 | 79 | # Chutzpah Test files 80 | _Chutzpah* 81 | 82 | # Visual C++ cache files 83 | ipch/ 84 | *.aps 85 | *.ncb 86 | *.opensdf 87 | *.sdf 88 | *.cachefile 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | 95 | # TFS 2012 Local Workspace 96 | $tf/ 97 | 98 | # Guidance Automation Toolkit 99 | *.gpState 100 | 101 | # ReSharper is a .NET coding add-in 102 | _ReSharper*/ 103 | *.[Rr]e[Ss]harper 104 | *.DotSettings.user 105 | 106 | # JustCode is a .NET coding addin-in 107 | .JustCode 108 | 109 | # TeamCity is a build add-in 110 | _TeamCity* 111 | 112 | # DotCover is a Code Coverage Tool 113 | *.dotCover 114 | 115 | # NCrunch 116 | _NCrunch_* 117 | .*crunch*.local.xml 118 | 119 | # MightyMoose 120 | *.mm.* 121 | AutoTest.Net/ 122 | 123 | # Web workbench (sass) 124 | .sass-cache/ 125 | 126 | # Installshield output folder 127 | [Ee]xpress/ 128 | 129 | # DocProject is a documentation generator add-in 130 | DocProject/buildhelp/ 131 | DocProject/Help/*.HxT 132 | DocProject/Help/*.HxC 133 | DocProject/Help/*.hhc 134 | DocProject/Help/*.hhk 135 | DocProject/Help/*.hhp 136 | DocProject/Help/Html2 137 | DocProject/Help/html 138 | 139 | # Click-Once directory 140 | publish/ 141 | 142 | # Publish Web Output 143 | *.[Pp]ublish.xml 144 | *.azurePubxml 145 | # TODO: Comment the next line if you want to checkin your web deploy settings 146 | # but database connection strings (with potential passwords) will be unencrypted 147 | *.pubxml 148 | *.publishproj 149 | 150 | # NuGet Packages 151 | *.nupkg 152 | # The packages folder can be ignored because of Package Restore 153 | **/packages/* 154 | # except build/, which is used as an MSBuild target. 155 | !**/packages/build/ 156 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 157 | #!**/packages/repositories.config 158 | 159 | # Windows Azure Build Output 160 | csx/ 161 | *.build.csdef 162 | 163 | # Windows Store app package directory 164 | AppPackages/ 165 | 166 | # Others 167 | sql/ 168 | *.Cache 169 | ClientBin/ 170 | [Ss]tyle[Cc]op.* 171 | ~$* 172 | *~ 173 | *.dbmdl 174 | *.dbproj.schemaview 175 | *.pfx 176 | *.publishsettings 177 | node_modules/ 178 | 179 | # RIA/Silverlight projects 180 | Generated_Code/ 181 | 182 | # Backup & report files from converting an old project file 183 | # to a newer Visual Studio version. Backup files are not needed, 184 | # because we have git ;-) 185 | _UpgradeReport_Files/ 186 | Backup*/ 187 | UpgradeLog*.XML 188 | UpgradeLog*.htm 189 | 190 | # SQL Server files 191 | *.mdf 192 | *.ldf 193 | 194 | # Business Intelligence projects 195 | *.rdl.data 196 | *.bim.layout 197 | *.bim_*.settings 198 | 199 | # Microsoft Fakes 200 | FakesAssemblies/ 201 | 202 | # ========================= 203 | # Operating System Files 204 | # ========================= 205 | 206 | # OSX 207 | # ========================= 208 | 209 | .DS_Store 210 | .AppleDouble 211 | .LSOverride 212 | 213 | # Thumbnails 214 | ._* 215 | 216 | # Files that might appear on external disk 217 | .Spotlight-V100 218 | .Trashes 219 | 220 | # Directories potentially created on remote AFP share 221 | .AppleDB 222 | .AppleDesktop 223 | Network Trash Folder 224 | Temporary Items 225 | .apdisk 226 | 227 | # Windows 228 | # ========================= 229 | 230 | # Windows image file caches 231 | Thumbs.db 232 | ehthumbs.db 233 | 234 | # Folder config file 235 | Desktop.ini 236 | 237 | # Recycle Bin used on file shares 238 | $RECYCLE.BIN/ 239 | 240 | # Windows Installer files 241 | *.cab 242 | *.msi 243 | *.msm 244 | *.msp 245 | 246 | # Windows shortcuts 247 | *.lnk 248 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language : cpp 2 | 3 | matrix: 4 | include: 5 | - os: osx 6 | osx_image: xcode9.1 7 | compiler: clang 8 | - os: linux 9 | env: CLANG_VERSION=5.0 BUILD_TYPE=Debug 10 | addons: &clang5 11 | apt: 12 | packages: 13 | - g++-6 14 | - clang-5.0 15 | - libx11-dev 16 | - libxfixes-dev 17 | - libxtst-dev 18 | - libxext-dev 19 | - libxinerama-dev 20 | - nasm 21 | sources: 22 | - ubuntu-toolchain-r-test 23 | - llvm-toolchain-precise 24 | 25 | before_install: 26 | - if [[ -n "$CLANG_VERSION" ]]; then export CXX=clang++-$CLANG_VERSION CC=clang-$CLANG_VERSION; fi 27 | - | 28 | if [ "${TRAVIS_OS_NAME}" == "linux" ]; then 29 | CMAKE_URL="https://cmake.org/files/v3.9/cmake-3.9.6-Linux-x86_64.tar.gz" 30 | mkdir -p ${TRAVIS_BUILD_DIR}/deps/cmake && travis_retry wget --no-check-certificate --quiet -O - "${CMAKE_URL}" | tar --strip-components=1 -xz -C ${TRAVIS_BUILD_DIR}/deps/cmake 31 | export PATH="${TRAVIS_BUILD_DIR}/deps/cmake/bin:${PATH}"; 32 | else 33 | brew update && brew upgrade cmake || brew install cmake; 34 | brew install openssl zlib libjpeg-turbo; 35 | fi 36 | - npm install -g typescript electron @angular/cli@latest; 37 | 38 | script: 39 | - mkdir build 40 | - cd build 41 | - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then 42 | cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_SHARED_LIBS=ON -DCMAKE_CXX_COMPILER="$CXX" -DCMAKE_C_COMPILER="$CC" -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl .. && make; 43 | else 44 | cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_SHARED_LIBS=ON -DCMAKE_CXX_COMPILER="$CXX" -DCMAKE_C_COMPILER="$CC" .. && make; 45 | ctest -V -C Debug; 46 | cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_SHARED_LIBS=OFF -DCMAKE_CXX_COMPILER="$CXX" -DCMAKE_C_COMPILER="$CC" .. && make; 47 | ctest -V -C Debug; 48 | fi 49 | 50 | notifications: 51 | email: false 52 | 53 | -------------------------------------------------------------------------------- /CMakeLists-ExternalProjects.txt: -------------------------------------------------------------------------------- 1 | include(ExternalProject) 2 | 3 | ExternalProject_Add( 4 | websocket_lite 5 | 6 | GIT_REPOSITORY "https://github.com/smasherprog/websocket_lite.git" 7 | GIT_TAG "master" 8 | 9 | UPDATE_COMMAND "" 10 | PATCH_COMMAND "" 11 | 12 | SOURCE_DIR "${CMAKE_BINARY_DIR}/libs/websocket_lite" 13 | CMAKE_ARGS -DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS} -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/bin 14 | 15 | TEST_COMMAND "" 16 | ) 17 | 18 | ExternalProject_Add( 19 | input_lite 20 | 21 | GIT_REPOSITORY "https://github.com/smasherprog/input_lite.git" 22 | GIT_TAG "3.0.0" 23 | 24 | UPDATE_COMMAND "" 25 | PATCH_COMMAND "" 26 | 27 | SOURCE_DIR "${CMAKE_BINARY_DIR}/libs/input_lite" 28 | CMAKE_ARGS -DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS} -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/bin 29 | 30 | TEST_COMMAND "" 31 | ) 32 | 33 | ExternalProject_Add( 34 | clipboard_lite 35 | 36 | GIT_REPOSITORY "https://github.com/smasherprog/clipboard_lite.git" 37 | GIT_TAG "3.4" 38 | 39 | UPDATE_COMMAND "" 40 | PATCH_COMMAND "" 41 | 42 | SOURCE_DIR "${CMAKE_BINARY_DIR}/libs/clipboard_lite" 43 | CMAKE_ARGS -DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS} -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/bin 44 | 45 | TEST_COMMAND "" 46 | ) 47 | 48 | ExternalProject_Add( 49 | screen_capture_lite 50 | 51 | GIT_REPOSITORY "https://github.com/smasherprog/screen_capture_lite.git" 52 | GIT_TAG "15.0.1" 53 | 54 | UPDATE_COMMAND "" 55 | PATCH_COMMAND "" 56 | 57 | SOURCE_DIR "${CMAKE_BINARY_DIR}/libs/screen_capture_lite" 58 | CMAKE_ARGS -DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS} -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/bin 59 | 60 | TEST_COMMAND "" 61 | ) 62 | 63 | 64 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | project(rat_lite) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | set(CMAKE_CXX_EXTENSIONS OFF) 7 | set(BUILD_EXAMPLE ON) 8 | option(BUILD_SHARED_LIBS "Build shared library" OFF) 9 | set (CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/modules") 10 | 11 | include(CMakeLists-ExternalProjects.txt) 12 | 13 | if(WIN32) 14 | set(REMOTE_ACCESS_PLATFORM_INC 15 | include/windows 16 | ) 17 | add_definitions(-D_CRT_SECURE_NO_WARNINGS -DNOMINMAX) 18 | elseif(APPLE) 19 | set(REMOTE_ACCESS_PLATFORM_INC 20 | include/ios 21 | ) 22 | else() 23 | set(REMOTE_ACCESS_PLATFORM_INC 24 | include/linux 25 | ) 26 | endif() 27 | 28 | if(MSVC) 29 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") 30 | else() 31 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic") 32 | endif() 33 | 34 | find_package(ZLIB REQUIRED) 35 | find_package(OpenSSL REQUIRED) 36 | find_package(LIBJPEGTURBO REQUIRED) 37 | 38 | set(COMMON_INCLUDE_DIRS 39 | include 40 | ${CMAKE_BINARY_DIR}/bin/include 41 | ${OPENSSL_INCLUDE_DIR} 42 | ${ZLIB_INCLUDE_DIRS} 43 | ${LIBJPEGTURBO_INCLUDE_DIRS} 44 | ) 45 | include_directories( 46 | ${COMMON_INCLUDE_DIRS} 47 | ) 48 | 49 | add_library(${PROJECT_NAME} 50 | include/RAT.h 51 | src/ClientDriver.cpp 52 | src/ServerDriver.cpp 53 | ) 54 | add_dependencies(${PROJECT_NAME} websocket_lite input_lite clipboard_lite screen_capture_lite) 55 | if(WIN32) 56 | target_link_libraries(${PROJECT_NAME} Crypt32 Dwmapi) 57 | elseif(APPLE) 58 | find_library(corefoundation_lib CoreFoundation) 59 | find_library(cocoa_lib Cocoa) 60 | find_package(Threads REQUIRED) 61 | target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT} ${corefoundation_lib} ${cocoa_lib}) 62 | else() 63 | find_package(X11 REQUIRED) 64 | find_package(Threads REQUIRED) 65 | target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT} dl ${X11_LIBRARIES} ${X11_Xfixes_LIB} ${X11_XTest_LIB} ${X11_Xinerama_LIB}) 66 | endif() 67 | 68 | if(${BUILD_SHARED_LIBS}) 69 | set_target_properties(${PROJECT_NAME} PROPERTIES DEFINE_SYMBOL RAT_LITE_DLL) 70 | endif() 71 | if(WIN32) 72 | set(LIBEXTENSION .lib) 73 | else() 74 | if(${BUILD_SHARED_LIBS}) 75 | set(LIBEXTENSION ${CMAKE_SHARED_LIBRARY_SUFFIX}) 76 | else() 77 | set(LIBEXTENSION ${CMAKE_STATIC_LIBRARY_SUFFIX}) 78 | endif() 79 | endif() 80 | if(${BUILD_SHARED_LIBS}) 81 | target_link_libraries(${PROJECT_NAME} 82 | ${CMAKE_BINARY_DIR}/bin/lib/${CMAKE_SHARED_LIBRARY_PREFIX}websocket_lite${LIBEXTENSION} 83 | ${CMAKE_BINARY_DIR}/bin/lib/${CMAKE_SHARED_LIBRARY_PREFIX}input_lite${LIBEXTENSION} 84 | ${CMAKE_BINARY_DIR}/bin/lib/${CMAKE_SHARED_LIBRARY_PREFIX}clipboard_lite${LIBEXTENSION} 85 | ${CMAKE_BINARY_DIR}/bin/lib/${CMAKE_SHARED_LIBRARY_PREFIX}screen_capture_lite${LIBEXTENSION} 86 | ${OPENSSL_LIBRARIES} 87 | ${ZLIB_LIBRARIES} 88 | ${LIBJPEGTURBO_LIBRARIES} 89 | ) 90 | endif() 91 | 92 | install (TARGETS ${PROJECT_NAME} 93 | RUNTIME DESTINATION bin 94 | ARCHIVE DESTINATION lib 95 | LIBRARY DESTINATION lib 96 | ) 97 | 98 | install (FILES 99 | include/RAT.h 100 | include/Logging.h 101 | DESTINATION include 102 | ) 103 | 104 | if(BUILD_EXAMPLE) 105 | add_subdirectory(src/server) 106 | add_subdirectory(src/client) 107 | endif() 108 | 109 | enable_testing() 110 | add_subdirectory(test) 111 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Scott 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## [Getting Started](https://github.com/smasherprog/rat_lite/wiki) 2 | ### I am not updating this regularly, if anyone wants to take over, please issue PR 3 | ### All development happens in the master branch. To get stable releases, use tags by going to the release section. 4 |

Linux/Mac

5 |

Windows

6 | 7 |

Windows Connecting to Mac
8 | 9 | 10 |
Mac Connecting to Windows
11 | 12 | 13 |
Windows Connecting to Android
14 | 15 | 16 | 17 |
Windows Connecting to Linux
18 | 19 | 20 |
Linux Connecting to Windows
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /include/Logging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #if __ANDROID__ 7 | 8 | #define APPNAME "RAT" 9 | #include 10 | 11 | #endif 12 | 13 | namespace SL { 14 | namespace RAT_Lite { 15 | 16 | enum Logging_Levels { Debug_log_level, ERROR_log_level, FATAL_log_level, INFO_log_level, WARN_log_level }; 17 | const std::string Logging_level_Names[] = {"DEBUG", "ERROR", "FATAL", "INFO", "WARN"}; 18 | inline void Log(Logging_Levels level, const char *file, int line, const char *func, std::ostringstream &data) 19 | { 20 | #if __ANDROID__ 21 | 22 | std::ostringstream buffer; 23 | buffer << Logging_level_Names[level] << ": FILE: " << file << " Line: " << line << " Func: " << func << " Msg: " << data.str(); 24 | auto msg = buffer.str(); 25 | switch (level) { 26 | case (Debug_log_level): 27 | __android_log_print(ANDROID_LOG_DEBUG, APPNAME, "%s", msg.c_str()); 28 | break; 29 | case (ERROR_log_level): 30 | __android_log_print(ANDROID_LOG_ERROR, APPNAME, "%s", msg.c_str()); 31 | break; 32 | case (FATAL_log_level): 33 | __android_log_print(ANDROID_LOG_FATAL, APPNAME, "%s", msg.c_str()); 34 | break; 35 | case (INFO_log_level): 36 | __android_log_print(ANDROID_LOG_INFO, APPNAME, "%s", msg.c_str()); 37 | break; 38 | case (WARN_log_level): 39 | __android_log_print(ANDROID_LOG_WARN, APPNAME, "%s", msg.c_str()); 40 | break; 41 | default: 42 | __android_log_print(ANDROID_LOG_INFO, APPNAME, "%s", msg.c_str()); 43 | } 44 | #else 45 | std::cout << Logging_level_Names[level] << ": FILE: " << file << " Line: " << line << " Func: " << func << " Msg: " << data.str() 46 | << std::endl; 47 | 48 | #endif 49 | } 50 | 51 | } // namespace RAT_Lite 52 | } // namespace SL 53 | #define UNUSED(x) (void)(x) 54 | #define S(x) #x 55 | #define S_(x) S(x) 56 | // Usage SL_RAT_LOG(SL::RAT::Logging_Levels::Debug_log_level, "Message goes here "<< 56 <<" Any Valid cout stuff works"); 57 | 58 | #define SL_RAT_LOG(level, msg) \ 59 | { \ 60 | \ 61 | std::ostringstream buffersl134nonesd; \ 62 | \ 63 | buffersl134nonesd \ 64 | << msg; \ 65 | \ 66 | SL::RAT_Lite::Log(level, __FILE__, __LINE__, __func__, buffersl134nonesd); \ 67 | \ 68 | } 69 | -------------------------------------------------------------------------------- /include/RAT.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Input_Lite.h" 3 | #include "ScreenCapture.h" 4 | #include "WS_Lite.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #if defined(WINDOWS) || defined(WIN32) 12 | #if defined(RAT_LITE_DLL) 13 | #define RAT_LITE_EXTERN __declspec(dllexport) 14 | #else 15 | #define RAT_LITE_EXTERN 16 | #endif 17 | #else 18 | #define RAT_LITE_EXTERN 19 | #endif 20 | 21 | namespace SL { 22 | namespace RAT_Lite { 23 | 24 | const int PixelStride = 4; 25 | struct Point { 26 | Point() : X(0), Y(0) {} 27 | Point(const Point &p) : Point(p.X, p.Y) {} 28 | Point(int x, int y) : X(x), Y(y) {} 29 | int X, Y; 30 | }; 31 | typedef Point Size; 32 | class Rect { 33 | 34 | public: 35 | Rect() : Height(0), Width(0) {} 36 | Rect(const Rect &rect) : Rect(rect.Origin, rect.Height, rect.Width) {} 37 | Rect(Point origin, int height, int width) : Origin(origin), Height(height), Width(width) {} 38 | auto Center() const { return Point(Origin.X + (Width / 2), Origin.Y + (Height / 2)); } 39 | 40 | Point Origin; 41 | int Height, Width; 42 | auto top() const { return Origin.Y; } 43 | auto bottom() const { return Origin.Y + Height; } 44 | void bottom(int b) { Height = b - Origin.Y; } 45 | auto left() const { return Origin.X; } 46 | auto right() const { return Origin.X + Width; } 47 | void right(int r) { Width = r - Origin.X; } 48 | auto Contains(const Point &p) const 49 | { 50 | if (p.X < left()) 51 | return false; 52 | if (p.Y < top()) 53 | return false; 54 | if (p.X >= right()) 55 | return false; 56 | if (p.Y >= bottom()) 57 | return false; 58 | return true; 59 | } 60 | void Expand_To_Include(const Point &p) 61 | { 62 | if (p.X <= left()) 63 | Origin.X = p.X; 64 | if (p.Y <= top()) 65 | Origin.Y = p.Y; 66 | if (right() <= p.X) 67 | Width = p.X - left(); 68 | if (bottom() <= p.Y) 69 | Height = p.Y - top(); 70 | } 71 | }; 72 | inline auto operator==(const Point &p1, const Point &p2) { return p1.X == p2.X && p1.Y == p2.Y; } 73 | inline auto operator!=(const Point &p1, const Point &p2) { return !(p1 == p2); } 74 | inline auto operator==(const Rect &p1, const Rect &p2) { return p1.Origin == p2.Origin && p1.Height == p2.Height && p1.Width == p2.Width; } 75 | inline auto SquaredDistance(const Point &p1, const Point &p2) 76 | { 77 | auto dx = abs(p1.X - p2.X); 78 | auto dy = abs(p1.Y - p2.Y); 79 | return dx * dx + dy * dy; 80 | } 81 | inline auto SquaredDistance(const Point &p, const Rect &r) 82 | { 83 | auto cx = std::max(std::min(p.X, r.right()), r.left()); 84 | auto cy = std::max(std::min(p.Y, r.bottom()), r.top()); 85 | return SquaredDistance(Point(cx, cy), p); 86 | } 87 | inline auto Distance(const Point &p1, const Point &p2) { return sqrt(SquaredDistance(p1, p2)); } 88 | 89 | inline auto Distance(const Point &p, const Rect &r) { return Distance(p, r.Center()); } 90 | struct Image { 91 | Image() {} 92 | Image(const Rect &r, const unsigned char *d, const size_t &l) : Rect_(r), Data(d), Length(l) {} 93 | Rect Rect_; 94 | const unsigned char *Data = nullptr; 95 | size_t Length = 0; 96 | }; 97 | 98 | enum Server_Status { SERVER_RUNNING, SERVER_STOPPING, SERVER_STOPPED }; 99 | enum class ImageEncoding : unsigned char { COLOR, GRAYSCALE }; 100 | enum class ClipboardSharing : unsigned char { NOT_SHARED, SHARED }; 101 | enum class PACKET_TYPES : unsigned int { 102 | INVALID, 103 | HTTP_MSG, 104 | ONMONITORSCHANGED, 105 | ONFRAMECHANGED, 106 | ONNEWFRAME, 107 | ONMOUSEIMAGECHANGED, 108 | ONMOUSEPOSITIONCHANGED, 109 | ONKEYUP, 110 | ONKEYDOWN, 111 | ONMOUSEUP, 112 | ONMOUSEDOWN, 113 | ONMOUSESCROLL, 114 | ONCLIPBOARDTEXTCHANGED, 115 | ONCLIENTSETTINGSCHANGED, 116 | // use LAST_PACKET_TYPE as the starting point of your custom packet types. Everything before this is used internally by the library 117 | LAST_PACKET_TYPE 118 | }; 119 | 120 | struct ClientSettings { 121 | ClipboardSharing ShareClip = ClipboardSharing::NOT_SHARED; 122 | int ImageCompressionSetting = 70; 123 | ImageEncoding EncodeImagesAsGrayScale = ImageEncoding::COLOR; 124 | std::vector MonitorsToWatch; 125 | }; 126 | template class INetworkHandlers { 127 | public: 128 | virtual ~INetworkHandlers() {} 129 | 130 | virtual std::shared_ptr onConnection(const std::function)> &callback) = 0; 131 | virtual std::shared_ptr 132 | onMessage(const std::function &socket, const WS_LITE::WSMessage)> &callback) = 0; 133 | virtual std::shared_ptr onDisconnection( 134 | const std::function &socket, unsigned short code, const std::string)> &callback) = 0; 135 | }; 136 | class RAT_LITE_EXTERN IConfig { 137 | public: 138 | virtual ~IConfig() {} 139 | virtual void ShareClipboard(ClipboardSharing share) = 0; 140 | virtual ClipboardSharing ShareClipboard() const = 0; 141 | }; 142 | class RAT_LITE_EXTERN IServerDriver : public IConfig { 143 | public: 144 | virtual ~IServerDriver() {} 145 | 146 | virtual void MaxConnections(int maxconnections) = 0; 147 | virtual int MaxConnections() const = 0; 148 | virtual size_t MemoryUsed() const = 0; 149 | virtual WS_LITE::WSMessage PrepareMonitorsChanged(const std::vector &monitors) = 0; 150 | // imagecompression is [0, 100] = [WORST, BEST] Better compression also takes more time .. 70 seems to work well 151 | virtual WS_LITE::WSMessage PrepareFrameChanged(const Screen_Capture::Image &image, const Screen_Capture::Monitor &monitor, 152 | int imagecompression = 70, bool usegrayscale = false) = 0; 153 | // imagecompression is [0, 100] = [WORST, BEST] 154 | virtual WS_LITE::WSMessage PrepareNewFrame(const Screen_Capture::Image &image, const Screen_Capture::Monitor &monitor, 155 | int imagecompression = 70, bool usegrayscale = false) = 0; 156 | virtual WS_LITE::WSMessage PrepareMouseImageChanged(const Screen_Capture::Image &image) = 0; 157 | virtual WS_LITE::WSMessage PrepareMousePositionChanged(const SL::Screen_Capture::Point &pos) = 0; 158 | virtual WS_LITE::WSMessage PrepareClipboardChanged(const std::string &text) = 0; 159 | }; 160 | 161 | class RAT_LITE_EXTERN IServerDriverConfiguration : public INetworkHandlers { 162 | public: 163 | virtual ~IServerDriverConfiguration() {} 164 | // events raised from the server 165 | virtual std::shared_ptr 166 | onKeyUp(const std::function &socket, Input_Lite::KeyCodes key)> &callback) = 0; 167 | virtual std::shared_ptr 168 | onKeyDown(const std::function &socket, Input_Lite::KeyCodes key)> &callback) = 0; 169 | virtual std::shared_ptr 170 | onMouseUp(const std::function &socket, Input_Lite::MouseButtons button)> &callback) = 0; 171 | virtual std::shared_ptr 172 | onMouseDown(const std::function &socket, Input_Lite::MouseButtons button)> &callback) = 0; 173 | virtual std::shared_ptr 174 | onMouseScroll(const std::function &socket, int offset)> &callback) = 0; 175 | virtual std::shared_ptr 176 | onMousePosition(const std::function &socket, const Point &pos)> &callback) = 0; 177 | virtual std::shared_ptr onClientSettingsChanged( 178 | const std::function &socket, const ClientSettings &settings)> &callback) = 0; 179 | 180 | virtual std::shared_ptr onClipboardChanged(const std::function &callback) = 0; 181 | virtual std::shared_ptr Build(const std::shared_ptr &wslistenerconfig) = 0; 182 | }; 183 | 184 | class RAT_LITE_EXTERN IClientDriver : public IConfig { 185 | public: 186 | virtual ~IClientDriver() {} 187 | 188 | virtual void SendKeyUp(SL::Input_Lite::KeyCodes key) = 0; 189 | virtual void SendKeyDown(SL::Input_Lite::KeyCodes key) = 0; 190 | virtual void SendMouseUp(const Input_Lite::MouseButtons button) = 0; 191 | virtual void SendMouseDown(const Input_Lite::MouseButtons button) = 0; 192 | virtual void SendMouseScroll(int offset) = 0; 193 | virtual void SendMousePosition(const Point &pos) = 0; 194 | virtual void SendClipboardChanged(const std::string &text) = 0; 195 | }; 196 | 197 | class RAT_LITE_EXTERN IClientDriverConfiguration : public INetworkHandlers { 198 | public: 199 | virtual ~IClientDriverConfiguration() {} 200 | virtual std::shared_ptr 201 | onMonitorsChanged(const std::function &)> &callback) = 0; 202 | virtual std::shared_ptr 203 | onFrameChanged(const std::function &callback) = 0; 204 | virtual std::shared_ptr 205 | onNewFrame(const std::function &callback) = 0; 206 | virtual std::shared_ptr onMouseImageChanged(const std::function &callback) = 0; 207 | virtual std::shared_ptr onMousePositionChanged(const std::function &callback) = 0; 208 | virtual std::shared_ptr onClipboardChanged(const std::function &callback) = 0; 209 | virtual std::shared_ptr Build(const std::shared_ptr &wsclientconfig) = 0; 210 | }; 211 | 212 | std::shared_ptr RAT_LITE_EXTERN CreateClientDriverConfiguration(); 213 | std::shared_ptr RAT_LITE_EXTERN CreateServerDriverConfiguration(); 214 | 215 | } // namespace RAT_Lite 216 | } // namespace SL -------------------------------------------------------------------------------- /modules/FindLIBJPEGTURBO.cmake: -------------------------------------------------------------------------------- 1 | include (FindPackageHandleStandardArgs) 2 | 3 | find_path(LIBJPEGTURBO_INCLUDE_DIRS turbojpeg.h 4 | PATHS /usr/local/opt/jpeg-turbo/include 5 | /usr/include 6 | ) 7 | set(JPEG_NAMES jpeg turbojpeg) 8 | 9 | FOREACH(LIB ${JPEG_NAMES}) 10 | FIND_LIBRARY(FOUND_LIB_${LIB} ${LIB} 11 | PATHS /usr/local/opt/jpeg-turbo/lib 12 | /opt/libjpeg-turbo/lib64 13 | /opt/libjpeg-turbo/lib 14 | ) 15 | if(FOUND_LIB_${LIB}) 16 | LIST(APPEND LIBJPEGTURBO_LIBRARIES ${FOUND_LIB_${LIB}}) 17 | endif() 18 | 19 | ENDFOREACH(LIB) 20 | 21 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBJPEGTURBO DEFAULT_MSG LIBJPEGTURBO_LIBRARIES LIBJPEGTURBO_INCLUDE_DIRS) 22 | 23 | mark_as_advanced(LIBJPEGTURBO_LIBRARIES LIBJPEGTURBO_INCLUDE_DIRS) -------------------------------------------------------------------------------- /src/ClientDriver.cpp: -------------------------------------------------------------------------------- 1 | #include "RAT.h" 2 | #include "turbojpeg.h" 3 | 4 | #include "Input_Lite.h" 5 | #include "Logging.h" 6 | #include "ScreenCapture.h" 7 | #include "WS_Lite.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace SL { 13 | namespace RAT_Lite { 14 | 15 | class ClientDriver : public IClientDriver { 16 | 17 | Point LastMousePosition_; 18 | std::shared_ptr Socket_; 19 | 20 | std::mutex outputbufferLock; 21 | std::vector outputbuffer; 22 | std::vector Monitors; 23 | 24 | void MouseImageChanged(const std::shared_ptr &socket, const unsigned char *data, size_t len) 25 | { 26 | if (!onMouseImageChanged) 27 | return; 28 | if (len >= sizeof(Rect)) { 29 | Image img(*reinterpret_cast(data), data + sizeof(Rect), len - sizeof(Rect)); 30 | if (len >= sizeof(Rect) + (img.Rect_.Width * img.Rect_.Height * PixelStride)) { 31 | return onMouseImageChanged(img); 32 | } 33 | } 34 | socket->close(1000, "Received invalid lenght on onMouseImageChanged"); 35 | } 36 | void MousePositionChanged(const std::shared_ptr &socket, const unsigned char *data, size_t len) 37 | { 38 | if (!onMousePositionChanged) 39 | return; 40 | if (len == sizeof(SL::Screen_Capture::Point)) { 41 | return onMousePositionChanged(*reinterpret_cast(data)); 42 | } 43 | socket->close(1000, "Received invalid lenght on onMousePositionChanged"); 44 | } 45 | 46 | void MonitorsChanged(const std::shared_ptr &socket, const unsigned char *data, size_t len) 47 | { 48 | if (!onMonitorsChanged) 49 | return; 50 | auto num = len / sizeof(Screen_Capture::Monitor); 51 | Monitors.clear(); 52 | if (len == num * sizeof(Screen_Capture::Monitor) && num < 8) { 53 | for (decltype(num) i = 0; i < num; i++) { 54 | Monitors.push_back(*(Screen_Capture::Monitor *)data); 55 | data += sizeof(Screen_Capture::Monitor); 56 | } 57 | return onMonitorsChanged(Monitors); 58 | } 59 | else if (len == 0) { 60 | // it is possible to have no monitors.. shouldnt disconnect in that case 61 | return onMonitorsChanged(Monitors); 62 | } 63 | socket->close(1000, "Invalid Monitor Count"); 64 | } 65 | void ClipboardTextChanged(const unsigned char *data, size_t len) 66 | { 67 | if (ShareClip == ClipboardSharing::NOT_SHARED || !onClipboardChanged) 68 | return; 69 | if (len < 1024 * 100) { // 100K max 70 | std::string str(reinterpret_cast(data), len); 71 | onClipboardChanged(str); 72 | } 73 | } 74 | template void Frame(const unsigned char *data, size_t len, CALLBACKTYPE &&cb) 75 | { 76 | int monitor_id = 0; 77 | if (len < sizeof(Rect) + sizeof(monitor_id)) { 78 | return Socket_->close(1000, "Invalid length on onFrameChanged"); 79 | } 80 | 81 | auto jpegDecompressor = tjInitDecompress(); 82 | int jpegSubsamp(0), outwidth(0), outheight(0); 83 | 84 | auto src = (unsigned char *)data; 85 | memcpy(&monitor_id, src, sizeof(monitor_id)); 86 | src += sizeof(monitor_id); 87 | Rect rect; 88 | memcpy(&rect, src, sizeof(rect)); 89 | src += sizeof(rect); 90 | 91 | auto monitor = std::find_if(begin(Monitors), end(Monitors), [monitor_id](const auto &m) { return m.Id == monitor_id; }); 92 | if (monitor == end(Monitors)) { 93 | SL_RAT_LOG(RAT_Lite::Logging_Levels::INFO_log_level, "Monitor Id doesnt exist!"); 94 | return; 95 | } 96 | len -= sizeof(Rect) + sizeof(monitor_id); 97 | 98 | if (tjDecompressHeader2(jpegDecompressor, src, static_cast(len), &outwidth, &outheight, &jpegSubsamp) == -1) { 99 | SL_RAT_LOG(RAT_Lite::Logging_Levels::ERROR_log_level, tjGetErrorStr()); 100 | } 101 | std::lock_guard lock(outputbufferLock); 102 | outputbuffer.reserve(outwidth * outheight * PixelStride); 103 | 104 | if (tjDecompress2(jpegDecompressor, src, static_cast(len), (unsigned char *)outputbuffer.data(), outwidth, 0, outheight, 105 | TJPF_RGBX, 2048 | TJFLAG_NOREALLOC) == -1) { 106 | SL_RAT_LOG(RAT_Lite::Logging_Levels::ERROR_log_level, tjGetErrorStr()); 107 | } 108 | Image img(rect, outputbuffer.data(), outwidth * outheight * PixelStride); 109 | 110 | assert(outwidth == img.Rect_.Width && outheight == img.Rect_.Height); 111 | cb(img, *monitor); 112 | tjDestroy(jpegDecompressor); 113 | } 114 | 115 | public: 116 | std::function &)> onMonitorsChanged; 117 | std::function onFrameChanged; 118 | std::function onNewFrame; 119 | std::function onMouseImageChanged; 120 | std::function onMousePositionChanged; 121 | std::function onClipboardChanged; 122 | std::function)> onConnection; 123 | std::function &socket, const WS_LITE::WSMessage)> onMessage; 124 | std::function &socket, unsigned short code, const std::string)> onDisconnection; 125 | ClipboardSharing ShareClip = ClipboardSharing::NOT_SHARED; 126 | 127 | ClientDriver() {} 128 | virtual ~ClientDriver() {} 129 | void Build(const std::shared_ptr &wsclientconfig) 130 | { 131 | 132 | wsclientconfig 133 | ->onConnection([&](const std::shared_ptr &socket, const SL::WS_LITE::HttpHeader &header) { 134 | SL_RAT_LOG(RAT_Lite::Logging_Levels::INFO_log_level, "onConnection "); 135 | Socket_ = socket; 136 | if (onConnection) 137 | onConnection(socket); 138 | UNUSED(header); 139 | }) 140 | ->onDisconnection([&](const std::shared_ptr &socket, unsigned short code, const std::string &msg) { 141 | SL_RAT_LOG(RAT_Lite::Logging_Levels::INFO_log_level, "onDisconnection "); 142 | Socket_.reset(); 143 | if (onDisconnection) 144 | onDisconnection(socket, code, msg); 145 | }) 146 | ->onMessage([&](const std::shared_ptr &socket, const SL::WS_LITE::WSMessage &message) { 147 | auto p = PACKET_TYPES::INVALID; 148 | assert(message.len >= sizeof(p)); 149 | 150 | p = *reinterpret_cast(message.data); 151 | const auto datastart = message.data + sizeof(p); 152 | size_t datasize = message.len - sizeof(p); 153 | switch (p) { 154 | case PACKET_TYPES::ONMONITORSCHANGED: 155 | MonitorsChanged(socket, datastart, datasize); 156 | break; 157 | case PACKET_TYPES::ONFRAMECHANGED: 158 | if (onFrameChanged) { 159 | Frame(datastart, datasize, [&](const auto &img, const auto &monitor) { onFrameChanged(img, monitor); }); 160 | } 161 | break; 162 | case PACKET_TYPES::ONNEWFRAME: 163 | if (onNewFrame) { 164 | Frame(datastart, datasize, [&](const auto &img, const auto &monitor) { onNewFrame(img, monitor); }); 165 | } 166 | break; 167 | case PACKET_TYPES::ONMOUSEIMAGECHANGED: 168 | MouseImageChanged(socket, datastart, datasize); 169 | break; 170 | case PACKET_TYPES::ONMOUSEPOSITIONCHANGED: 171 | MousePositionChanged(socket, datastart, datasize); 172 | break; 173 | case PACKET_TYPES::ONCLIPBOARDTEXTCHANGED: 174 | ClipboardTextChanged(datastart, datasize); 175 | break; 176 | default: 177 | if (onMessage) 178 | onMessage(socket, message); // pass up the chain 179 | break; 180 | } 181 | 182 | }); 183 | } 184 | virtual void ShareClipboard(ClipboardSharing share) override { ShareClip = share; } 185 | virtual ClipboardSharing ShareClipboard() const override { return ShareClip; } 186 | template void SendStruct_Impl(STRUCT key, PACKET_TYPES ptype) 187 | { 188 | if (!Socket_) { 189 | SL_RAT_LOG(RAT_Lite::Logging_Levels::INFO_log_level, "SendKey called on a socket that is not open yet"); 190 | return; 191 | } 192 | const auto size = sizeof(ptype) + sizeof(key); 193 | auto ptr(std::shared_ptr(new unsigned char[size], [](auto *p) { delete[] p; })); 194 | *reinterpret_cast(ptr.get()) = ptype; 195 | memcpy(ptr.get() + sizeof(ptype), &key, sizeof(key)); 196 | 197 | SL::WS_LITE::WSMessage buf; 198 | buf.code = WS_LITE::OpCode::BINARY; 199 | buf.Buffer = ptr; 200 | buf.len = size; 201 | buf.data = ptr.get(); 202 | Socket_->send(buf, SL::WS_LITE::CompressionOptions::NO_COMPRESSION); 203 | } 204 | virtual void SendClipboardChanged(const std::string &text) override 205 | { 206 | if (!Socket_) { 207 | SL_RAT_LOG(RAT_Lite::Logging_Levels::INFO_log_level, "SendClipboardText called on a socket that is not open yet"); 208 | return; 209 | } 210 | if (Socket_->is_loopback()) 211 | return; // dont send clipboard info to ourselfs as it will cause a loop 212 | 213 | auto ptype = PACKET_TYPES::ONCLIPBOARDTEXTCHANGED; 214 | auto size = sizeof(ptype) + text.size(); 215 | auto ptr(std::shared_ptr(new unsigned char[size], [](auto *p) { delete[] p; })); 216 | *reinterpret_cast(ptr.get()) = ptype; 217 | memcpy(ptr.get() + sizeof(ptype), text.data(), text.size()); 218 | 219 | SL::WS_LITE::WSMessage buf; 220 | buf.code = WS_LITE::OpCode::BINARY; 221 | buf.Buffer = ptr; 222 | buf.len = size; 223 | buf.data = ptr.get(); 224 | Socket_->send(buf, SL::WS_LITE::CompressionOptions::NO_COMPRESSION); 225 | } 226 | virtual void SendKeyUp(Input_Lite::KeyCodes key) override { SendStruct_Impl(key, PACKET_TYPES::ONKEYUP); } 227 | virtual void SendKeyDown(Input_Lite::KeyCodes key) override { SendStruct_Impl(key, PACKET_TYPES::ONKEYDOWN); } 228 | virtual void SendMouseUp(const Input_Lite::MouseButtons button) override { SendStruct_Impl(button, PACKET_TYPES::ONMOUSEUP); } 229 | virtual void SendMouseDown(const Input_Lite::MouseButtons button) override { SendStruct_Impl(button, PACKET_TYPES::ONMOUSEDOWN); } 230 | virtual void SendMouseScroll(int offset) override { SendStruct_Impl(offset, PACKET_TYPES::ONMOUSESCROLL); } 231 | virtual void SendMousePosition(const Point &pos) override { SendStruct_Impl(pos, PACKET_TYPES::ONMOUSEPOSITIONCHANGED); } 232 | }; 233 | 234 | class ClientDriverConfiguration : public IClientDriverConfiguration { 235 | std::shared_ptr ClientDriverImpl; 236 | 237 | public: 238 | ClientDriverConfiguration(const std::shared_ptr &c) : ClientDriverImpl(c) {} 239 | virtual ~ClientDriverConfiguration() {} 240 | virtual std::shared_ptr 241 | onMonitorsChanged(const std::function &)> &callback) override 242 | { 243 | assert(!ClientDriverImpl->onMonitorsChanged); 244 | ClientDriverImpl->onMonitorsChanged = callback; 245 | return std::make_shared(ClientDriverImpl); 246 | } 247 | virtual std::shared_ptr 248 | onFrameChanged(const std::function &callback) override 249 | { 250 | assert(!ClientDriverImpl->onFrameChanged); 251 | ClientDriverImpl->onFrameChanged = callback; 252 | return std::make_shared(ClientDriverImpl); 253 | } 254 | virtual std::shared_ptr 255 | onNewFrame(const std::function &callback) override 256 | { 257 | assert(!ClientDriverImpl->onNewFrame); 258 | ClientDriverImpl->onNewFrame = callback; 259 | return std::make_shared(ClientDriverImpl); 260 | } 261 | virtual std::shared_ptr onMouseImageChanged(const std::function &callback) override 262 | { 263 | assert(!ClientDriverImpl->onMouseImageChanged); 264 | ClientDriverImpl->onMouseImageChanged = callback; 265 | return std::make_shared(ClientDriverImpl); 266 | } 267 | virtual std::shared_ptr onMousePositionChanged(const std::function &callback) override 268 | { 269 | assert(!ClientDriverImpl->onMousePositionChanged); 270 | ClientDriverImpl->onMousePositionChanged = callback; 271 | return std::make_shared(ClientDriverImpl); 272 | } 273 | virtual std::shared_ptr onClipboardChanged(const std::function &callback) override 274 | { 275 | assert(!ClientDriverImpl->onClipboardChanged); 276 | ClientDriverImpl->onClipboardChanged = callback; 277 | return std::make_shared(ClientDriverImpl); 278 | } 279 | 280 | virtual std::shared_ptr 281 | onConnection(const std::function)> &callback) override 282 | { 283 | assert(!ClientDriverImpl->onConnection); 284 | ClientDriverImpl->onConnection = callback; 285 | return std::make_shared(ClientDriverImpl); 286 | } 287 | virtual std::shared_ptr 288 | onMessage(const std::function &socket, const WS_LITE::WSMessage)> &callback) override 289 | { 290 | assert(!ClientDriverImpl->onMessage); 291 | ClientDriverImpl->onMessage = callback; 292 | return std::make_shared(ClientDriverImpl); 293 | } 294 | virtual std::shared_ptr onDisconnection( 295 | const std::function &socket, unsigned short code, const std::string)> &callback) 296 | override 297 | { 298 | assert(!ClientDriverImpl->onDisconnection); 299 | ClientDriverImpl->onDisconnection = callback; 300 | return std::make_shared(ClientDriverImpl); 301 | } 302 | virtual std::shared_ptr Build(const std::shared_ptr &wsclientconfig) override 303 | { 304 | ClientDriverImpl->Build(wsclientconfig); 305 | return ClientDriverImpl; 306 | } 307 | }; 308 | 309 | std::shared_ptr CreateClientDriverConfiguration() 310 | { 311 | return std::make_shared(std::make_shared()); 312 | } 313 | 314 | } // namespace RAT_Lite 315 | } // namespace SL 316 | -------------------------------------------------------------------------------- /src/Desktop_Server.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smasherprog/rat_lite/34bc5f9b6e2a5fd44e8223a88566726246bb00a8/src/Desktop_Server.ico -------------------------------------------------------------------------------- /src/ServerDriver.cpp: -------------------------------------------------------------------------------- 1 | #include "Input_Lite.h" 2 | #include "Logging.h" 3 | #include "RAT.h" 4 | #include "ScreenCapture.h" 5 | #include "WS_Lite.h" 6 | #include "turbojpeg.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace SL { 12 | namespace RAT_Lite { 13 | 14 | class ServerDriver : public IServerDriver { 15 | 16 | public: 17 | ServerDriver() { MemoryInUse = 0; } 18 | virtual ~ServerDriver() {} 19 | std::atomic ClientCount; 20 | ClipboardSharing ShareClip = ClipboardSharing::NOT_SHARED; 21 | int MaxNumConnections = 10; 22 | std::atomic MemoryInUse; 23 | std::function &socket, Input_Lite::KeyCodes key)> onKeyUp; 24 | std::function &socket, Input_Lite::KeyCodes key)> onKeyDown; 25 | std::function &socket, Input_Lite::MouseButtons button)> onMouseUp; 26 | std::function &socket, Input_Lite::MouseButtons button)> onMouseDown; 27 | std::function &socket, const ClientSettings &settings)> onClientSettingsChanged; 28 | std::function &socket, int offset)> onMouseScroll; 29 | std::function &socket, const Point &pos)> onMousePosition; 30 | std::function onClipboardChanged; 31 | 32 | std::function)> onConnection; 33 | std::function &socket, const WS_LITE::WSMessage)> onMessage; 34 | std::function &socket, unsigned short code, const std::string)> onDisconnection; 35 | 36 | std::shared_mutex MonitorsLock; 37 | std::vector Monitors; 38 | 39 | virtual void ShareClipboard(ClipboardSharing share) override { ShareClip = share; } 40 | virtual ClipboardSharing ShareClipboard() const override { return ShareClip; } 41 | virtual void MaxConnections(int maxconnections) override 42 | { 43 | assert(maxconnections >= 0); 44 | MaxNumConnections = maxconnections; 45 | } 46 | virtual int MaxConnections() const override { return MaxNumConnections; } 47 | virtual size_t MemoryUsed() const override { return MemoryInUse; } 48 | 49 | void KeyUp(const std::shared_ptr &socket, const unsigned char *data, size_t len) 50 | { 51 | if (!onKeyUp) 52 | return; 53 | if (len == sizeof(Input_Lite::KeyCodes)) { 54 | onKeyUp(socket, *reinterpret_cast(data)); 55 | } 56 | else { 57 | return socket->close(1000, "Received invalid onKeyUp Event"); 58 | } 59 | } 60 | void KeyDown(const std::shared_ptr &socket, const unsigned char *data, size_t len) 61 | { 62 | if (!onKeyDown) 63 | return; 64 | if (len == sizeof(Input_Lite::KeyCodes)) { 65 | onKeyDown(socket, *reinterpret_cast(data)); 66 | } 67 | else { 68 | return socket->close(1000, "Received invalid onKeyDown Event"); 69 | } 70 | } 71 | void MouseUp(const std::shared_ptr &socket, const unsigned char *data, size_t len) 72 | { 73 | if (!onMouseUp) 74 | return; 75 | if (len == sizeof(Input_Lite::MouseButtons)) { 76 | return onMouseUp(socket, *reinterpret_cast(data)); 77 | } 78 | socket->close(1000, "Received invalid onMouseUp Event"); 79 | } 80 | 81 | void MouseDown(const std::shared_ptr &socket, const unsigned char *data, size_t len) 82 | { 83 | if (!onMouseDown) 84 | return; 85 | if (len == sizeof(Input_Lite::MouseButtons)) { 86 | return onMouseDown(socket, *reinterpret_cast(data)); 87 | } 88 | socket->close(1000, "Received invalid onMouseDown Event"); 89 | } 90 | void MousePosition(const std::shared_ptr &socket, const unsigned char *data, size_t len) 91 | { 92 | if (!onMousePosition) 93 | return; 94 | if (len == sizeof(Point)) { 95 | auto p = *reinterpret_cast(data); 96 | return onMousePosition(socket, p); 97 | } 98 | socket->close(1000, "Received invalid onMouseDown Event"); 99 | } 100 | void MouseScroll(const std::shared_ptr &socket, const unsigned char *data, size_t len) 101 | { 102 | if (!onMouseScroll) 103 | return; 104 | if (len == sizeof(int)) { 105 | return onMouseScroll(socket, *reinterpret_cast(data)); 106 | } 107 | socket->close(1000, "Received invalid onMouseScroll Event"); 108 | } 109 | void ClientSettingsChanged(const std::shared_ptr &socket, const unsigned char *data, size_t len) 110 | { 111 | if (!onClientSettingsChanged) 112 | return; 113 | ClientSettings c; 114 | auto beginsize = sizeof(c.ShareClip) + sizeof(c.ImageCompressionSetting) + sizeof(c.EncodeImagesAsGrayScale); 115 | auto remainingdata = len - beginsize; 116 | if (len >= (beginsize + sizeof(int)) && // min size received at least 1 monitor 117 | (remainingdata % sizeof(int) == 0)) { // the remaining bytes are divisible by sizeof int 118 | auto start = data; 119 | // need to do a manualy copy because of byte packing 120 | memcpy(&c.ShareClip, start, sizeof(c.ShareClip)); 121 | start += sizeof(c.ShareClip); 122 | memcpy(&c.ImageCompressionSetting, start, sizeof(c.ImageCompressionSetting)); 123 | start += sizeof(c.ImageCompressionSetting); 124 | memcpy(&c.EncodeImagesAsGrayScale, start, sizeof(c.EncodeImagesAsGrayScale)); 125 | start += sizeof(c.EncodeImagesAsGrayScale); 126 | auto begin = reinterpret_cast(start); 127 | auto end = reinterpret_cast(start + remainingdata + sizeof(int)); 128 | std::shared_lock lock(MonitorsLock); 129 | for (auto b = begin; b < end; b++) { 130 | auto found = std::find_if(std::begin(Monitors), std::end(Monitors), [b](auto &mon) { return mon.Id == *b; }); 131 | if (found != std::end(Monitors)) { 132 | c.MonitorsToWatch.push_back(*found); 133 | } 134 | } 135 | return onClientSettingsChanged(socket, c); 136 | } 137 | socket->close(1000, "Received invalid onClientSettingsChanged Event"); 138 | } 139 | void ClipboardChanged(const unsigned char *data, size_t len) 140 | { 141 | if (!onClipboardChanged || ShareClip == ClipboardSharing::NOT_SHARED) 142 | return; 143 | if (len < 1024 * 100) { // 100K max 144 | std::string str(reinterpret_cast(data), len); 145 | return onClipboardChanged(str); 146 | } 147 | } 148 | 149 | void Build(const std::shared_ptr &wslistenerconfig) 150 | { 151 | 152 | ClientCount = 0; 153 | wslistenerconfig 154 | ->onConnection([&](const std::shared_ptr &socket, const SL::WS_LITE::HttpHeader &header) { 155 | UNUSED(header); 156 | if (MaxNumConnections > 0 && ClientCount + 1 > MaxNumConnections) { 157 | socket->close(1000, "Closing due to max number of connections!"); 158 | } 159 | else { 160 | if (onConnection) 161 | onConnection(socket); 162 | ClientCount += 1; 163 | } 164 | }) 165 | ->onDisconnection([&](const std::shared_ptr &socket, unsigned short code, const std::string &msg) { 166 | SL_RAT_LOG(RAT_Lite::Logging_Levels::INFO_log_level, "onDisconnection "); 167 | ClientCount -= 1; 168 | if (onDisconnection) 169 | onDisconnection(socket, code, msg); 170 | }) 171 | ->onMessage([&](const std::shared_ptr &socket, const WS_LITE::WSMessage &message) { 172 | 173 | auto p = PACKET_TYPES::INVALID; 174 | assert(message.len >= sizeof(p)); 175 | 176 | p = *reinterpret_cast(message.data); 177 | auto datastart = message.data + sizeof(p); 178 | auto datasize = message.len - sizeof(p); 179 | 180 | switch (p) { 181 | case PACKET_TYPES::ONKEYDOWN: 182 | KeyDown(socket, datastart, datasize); 183 | break; 184 | case PACKET_TYPES::ONKEYUP: 185 | KeyUp(socket, datastart, datasize); 186 | break; 187 | case PACKET_TYPES::ONMOUSEUP: 188 | MouseUp(socket, datastart, datasize); 189 | break; 190 | case PACKET_TYPES::ONMOUSEDOWN: 191 | MouseDown(socket, datastart, datasize); 192 | break; 193 | case PACKET_TYPES::ONMOUSEPOSITIONCHANGED: 194 | MousePosition(socket, datastart, datasize); 195 | break; 196 | case PACKET_TYPES::ONCLIENTSETTINGSCHANGED: 197 | ClientSettingsChanged(socket, datastart, datasize); 198 | break; 199 | case PACKET_TYPES::ONMOUSESCROLL: 200 | MouseScroll(socket, datastart, datasize); 201 | break; 202 | case PACKET_TYPES::ONCLIPBOARDTEXTCHANGED: 203 | ClipboardChanged(datastart, datasize); 204 | break; 205 | default: 206 | if (onMessage) 207 | onMessage(socket, message); 208 | break; 209 | } 210 | }); 211 | } 212 | 213 | WS_LITE::WSMessage PrepareImage(const Screen_Capture::Image &img, int imagecompression, bool usegrayscale, 214 | const Screen_Capture::Monitor &monitor, PACKET_TYPES p) 215 | { 216 | Rect r(Point(img.Bounds.left, img.Bounds.top), Height(img), Width(img)); 217 | auto set = usegrayscale ? TJSAMP_GRAY : TJSAMP_420; 218 | unsigned long maxsize = 219 | tjBufSize(Screen_Capture::Width(img), Screen_Capture::Height(img), set) + sizeof(r) + sizeof(p) + sizeof(monitor.Id); 220 | auto jpegCompressor = tjInitCompress(); 221 | MemoryInUse += maxsize; 222 | auto buffer = std::shared_ptr(new unsigned char[maxsize], [maxsize, this](auto *p) { 223 | delete[] p; 224 | MemoryInUse -= maxsize; 225 | }); 226 | auto dst = (unsigned char *)buffer.get(); 227 | memcpy(dst, &p, sizeof(p)); 228 | dst += sizeof(p); 229 | memcpy(dst, &monitor.Id, sizeof(monitor.Id)); 230 | dst += sizeof(monitor.Id); 231 | memcpy(dst, &r, sizeof(r)); 232 | dst += sizeof(r); 233 | auto srcbuffer = std::make_unique(RowStride(img) * Height(img)); 234 | Screen_Capture::Extract(img, srcbuffer.get(), RowStride(img) * Height(img)); 235 | auto srcbuf = (unsigned char *)srcbuffer.get(); 236 | auto colorencoding = TJPF_BGRX; 237 | auto outjpegsize = maxsize; 238 | 239 | if (tjCompress2(jpegCompressor, srcbuf, r.Width, 0, r.Height, colorencoding, &dst, &outjpegsize, set, imagecompression, 240 | TJFLAG_FASTDCT | TJFLAG_NOREALLOC) == -1) { 241 | SL_RAT_LOG(RAT_Lite::Logging_Levels::ERROR_log_level, tjGetErrorStr()); 242 | } 243 | // std::cout << "Sending " << r << std::endl; 244 | auto finalsize = sizeof(p) + sizeof(r) + sizeof(monitor.Id) + outjpegsize; // adjust the correct size 245 | tjDestroy(jpegCompressor); 246 | return WS_LITE::WSMessage{buffer.get(), finalsize, WS_LITE::OpCode::BINARY, buffer}; 247 | } 248 | 249 | virtual WS_LITE::WSMessage PrepareMonitorsChanged(const std::vector &monitors) override 250 | { 251 | { 252 | std::unique_lock lock(MonitorsLock); 253 | Monitors = monitors; 254 | } 255 | auto p = static_cast(PACKET_TYPES::ONMONITORSCHANGED); 256 | const auto finalsize = (monitors.size() * sizeof(Screen_Capture::Monitor)) + sizeof(p); 257 | 258 | MemoryInUse += finalsize; 259 | auto buffer = std::shared_ptr(new unsigned char[finalsize], [finalsize, this](auto *p) { 260 | delete[] p; 261 | MemoryInUse -= finalsize; 262 | }); 263 | 264 | auto buf = buffer.get(); 265 | memcpy(buf, &p, sizeof(p)); 266 | buf += sizeof(p); 267 | for (auto &a : monitors) { 268 | memcpy(buf, &a, sizeof(a)); 269 | buf += sizeof(Screen_Capture::Monitor); 270 | } 271 | return WS_LITE::WSMessage{buffer.get(), finalsize, WS_LITE::OpCode::BINARY, buffer}; 272 | } 273 | virtual WS_LITE::WSMessage PrepareFrameChanged(const Screen_Capture::Image &image, const Screen_Capture::Monitor &monitor, 274 | int imagecompression, bool usegrayscale) override 275 | { 276 | assert(imagecompression > 0 && imagecompression <= 100); 277 | return PrepareImage(image, imagecompression, usegrayscale, monitor, PACKET_TYPES::ONFRAMECHANGED); 278 | } 279 | virtual WS_LITE::WSMessage PrepareNewFrame(const Screen_Capture::Image &image, const Screen_Capture::Monitor &monitor, int imagecompression, 280 | bool usegrayscale) override 281 | { 282 | assert(imagecompression > 0 && imagecompression <= 100); 283 | return PrepareImage(image, imagecompression, usegrayscale, monitor, PACKET_TYPES::ONNEWFRAME); 284 | } 285 | virtual WS_LITE::WSMessage PrepareMouseImageChanged(const Screen_Capture::Image &image) override 286 | { 287 | Rect r(Point(0, 0), Height(image), Width(image)); 288 | auto p = static_cast(PACKET_TYPES::ONMOUSEIMAGECHANGED); 289 | auto finalsize = (Screen_Capture::RowStride(image) * Screen_Capture::Height(image)) + sizeof(p) + sizeof(r); 290 | MemoryInUse += finalsize; 291 | auto buffer = std::shared_ptr(new unsigned char[finalsize], [finalsize, this](auto *p) { 292 | delete[] p; 293 | MemoryInUse -= finalsize; 294 | }); 295 | auto dst = buffer.get(); 296 | memcpy(dst, &p, sizeof(p)); 297 | dst += sizeof(p); 298 | memcpy(dst, &r, sizeof(r)); 299 | dst += sizeof(r); 300 | Screen_Capture::Extract(image, (unsigned char *)dst, Screen_Capture::RowStride(image) * Screen_Capture::Height(image)); 301 | return WS_LITE::WSMessage{buffer.get(), finalsize, WS_LITE::OpCode::BINARY, buffer}; 302 | } 303 | virtual WS_LITE::WSMessage PrepareMousePositionChanged(const SL::Screen_Capture::Point &pos) override 304 | { 305 | auto p = static_cast(PACKET_TYPES::ONMOUSEPOSITIONCHANGED); 306 | const auto finalsize = sizeof(pos) + sizeof(p); 307 | MemoryInUse += finalsize; 308 | auto buffer = std::shared_ptr(new unsigned char[finalsize], [finalsize, this](auto *p) { 309 | delete[] p; 310 | MemoryInUse -= finalsize; 311 | }); 312 | memcpy(buffer.get(), &p, sizeof(p)); 313 | memcpy(buffer.get() + sizeof(p), &pos, sizeof(pos)); 314 | return WS_LITE::WSMessage{buffer.get(), finalsize, WS_LITE::OpCode::BINARY, buffer}; 315 | } 316 | virtual WS_LITE::WSMessage PrepareClipboardChanged(const std::string &text) override 317 | { 318 | auto p = static_cast(PACKET_TYPES::ONCLIPBOARDTEXTCHANGED); 319 | auto finalsize = text.size() + sizeof(p); 320 | MemoryInUse += finalsize; 321 | auto buffer = std::shared_ptr(new unsigned char[finalsize], [finalsize, this](auto *p) { 322 | delete[] p; 323 | MemoryInUse -= finalsize; 324 | }); 325 | memcpy(buffer.get(), &p, sizeof(p)); 326 | memcpy(buffer.get() + sizeof(p), text.data(), text.size()); 327 | return WS_LITE::WSMessage{buffer.get(), finalsize, WS_LITE::OpCode::BINARY, buffer}; 328 | } 329 | }; 330 | class ServerDriverConfiguration : public IServerDriverConfiguration { 331 | std::shared_ptr ServerDriver_; 332 | 333 | public: 334 | ServerDriverConfiguration(const std::shared_ptr &c) : ServerDriver_(c) {} 335 | virtual ~ServerDriverConfiguration() {} 336 | virtual std::shared_ptr 337 | onKeyUp(const std::function &socket, Input_Lite::KeyCodes key)> &callback) override 338 | { 339 | assert(!ServerDriver_->onKeyUp); 340 | ServerDriver_->onKeyUp = callback; 341 | return std::make_shared(ServerDriver_); 342 | } 343 | virtual std::shared_ptr 344 | onKeyDown(const std::function &socket, Input_Lite::KeyCodes key)> &callback) override 345 | { 346 | assert(!ServerDriver_->onKeyDown); 347 | ServerDriver_->onKeyDown = callback; 348 | return std::make_shared(ServerDriver_); 349 | } 350 | virtual std::shared_ptr 351 | onMouseUp(const std::function &socket, Input_Lite::MouseButtons button)> &callback) override 352 | { 353 | assert(!ServerDriver_->onMouseUp); 354 | ServerDriver_->onMouseUp = callback; 355 | return std::make_shared(ServerDriver_); 356 | } 357 | virtual std::shared_ptr 358 | onMouseDown(const std::function &socket, Input_Lite::MouseButtons button)> &callback) override 359 | { 360 | assert(!ServerDriver_->onMouseDown); 361 | ServerDriver_->onMouseDown = callback; 362 | return std::make_shared(ServerDriver_); 363 | } 364 | virtual std::shared_ptr 365 | onMouseScroll(const std::function &socket, int offset)> &callback) override 366 | { 367 | assert(!ServerDriver_->onMouseScroll); 368 | ServerDriver_->onMouseScroll = callback; 369 | return std::make_shared(ServerDriver_); 370 | } 371 | virtual std::shared_ptr 372 | onMousePosition(const std::function &socket, const Point &pos)> &callback) override 373 | { 374 | assert(!ServerDriver_->onMousePosition); 375 | ServerDriver_->onMousePosition = callback; 376 | return std::make_shared(ServerDriver_); 377 | } 378 | virtual std::shared_ptr onClipboardChanged(const std::function &callback) override 379 | { 380 | assert(!ServerDriver_->onClipboardChanged); 381 | ServerDriver_->onClipboardChanged = callback; 382 | return std::make_shared(ServerDriver_); 383 | } 384 | virtual std::shared_ptr 385 | onConnection(const std::function)> &callback) override 386 | { 387 | assert(!ServerDriver_->onConnection); 388 | ServerDriver_->onConnection = callback; 389 | return std::make_shared(ServerDriver_); 390 | } 391 | virtual std::shared_ptr 392 | onMessage(const std::function &socket, const WS_LITE::WSMessage)> &callback) override 393 | { 394 | assert(!ServerDriver_->onMessage); 395 | ServerDriver_->onMessage = callback; 396 | return std::make_shared(ServerDriver_); 397 | } 398 | virtual std::shared_ptr onDisconnection( 399 | const std::function &socket, unsigned short code, const std::string)> &callback) 400 | override 401 | { 402 | assert(!ServerDriver_->onDisconnection); 403 | ServerDriver_->onDisconnection = callback; 404 | return std::make_shared(ServerDriver_); 405 | } 406 | virtual std::shared_ptr onClientSettingsChanged( 407 | const std::function &socket, const ClientSettings &settings)> &callback) override 408 | { 409 | assert(!ServerDriver_->onClientSettingsChanged); 410 | ServerDriver_->onClientSettingsChanged = callback; 411 | return std::make_shared(ServerDriver_); 412 | } 413 | virtual std::shared_ptr Build(const std::shared_ptr &wslistenerconfig) override 414 | { 415 | ServerDriver_->Build(wslistenerconfig); 416 | return ServerDriver_; 417 | } 418 | }; 419 | 420 | std::shared_ptr CreateServerDriverConfiguration() 421 | { 422 | return std::make_shared(std::make_shared()); 423 | } 424 | 425 | } // namespace RAT_Lite 426 | } // namespace SL 427 | -------------------------------------------------------------------------------- /src/client/.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "ratclient", 5 | "ejected": true 6 | }, 7 | "apps": [ 8 | { 9 | "root": "src", 10 | "outDir": "dist", 11 | "assets": [ 12 | "assets", 13 | "favicon.ico" 14 | ], 15 | "index": "index.html", 16 | "main": "main.ts", 17 | "polyfills": "polyfills.ts", 18 | "test": "test.ts", 19 | "tsconfig": "tsconfig.app.json", 20 | "prefix": "app", 21 | "styles": [ 22 | "styles.scss" 23 | ], 24 | "scripts": [], 25 | "environmentSource": "environments/environment.ts", 26 | "environments": { 27 | "dev": "environments/environment.ts", 28 | "prod": "environments/environment.prod.ts" 29 | } 30 | } 31 | ], 32 | "defaults": { 33 | "styleExt": "css", 34 | "component": {} 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/client/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /src/client/.vscode/0.010801647396913694.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat" -no_logo -arch=x86 || exit 3 | echo CL := %CL% 4 | echo _CL_ := %_CL_% 5 | echo INCLUDE := %INCLUDE% 6 | echo LIBPATH := %LIBPATH% 7 | echo LINK := %LINK% 8 | echo _LINK_ := %_LINK_% 9 | echo LIB := %LIB% 10 | echo PATH := %PATH% 11 | echo TMP := %TMP% 12 | echo FRAMEWORKDIR := %FRAMEWORKDIR% 13 | echo FRAMEWORKDIR64 := %FRAMEWORKDIR64% 14 | echo FRAMEWORKVERSION := %FRAMEWORKVERSION% 15 | echo FRAMEWORKVERSION64 := %FRAMEWORKVERSION64% 16 | echo UCRTCONTEXTROOT := %UCRTCONTEXTROOT% 17 | echo UCRTVERSION := %UCRTVERSION% 18 | echo UNIVERSALCRTSDKDIR := %UNIVERSALCRTSDKDIR% 19 | echo VCINSTALLDIR := %VCINSTALLDIR% 20 | echo VCTARGETSPATH := %VCTARGETSPATH% 21 | echo WINDOWSLIBPATH := %WINDOWSLIBPATH% 22 | echo WINDOWSSDKDIR := %WINDOWSSDKDIR% 23 | echo WINDOWSSDKLIBVERSION := %WINDOWSSDKLIBVERSION% 24 | echo WINDOWSSDKVERSION := %WINDOWSSDKVERSION% -------------------------------------------------------------------------------- /src/client/.vscode/0.8898083114717834.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat" -no_logo -arch=amd64 || exit 3 | echo CL := %CL% 4 | echo _CL_ := %_CL_% 5 | echo INCLUDE := %INCLUDE% 6 | echo LIBPATH := %LIBPATH% 7 | echo LINK := %LINK% 8 | echo _LINK_ := %_LINK_% 9 | echo LIB := %LIB% 10 | echo PATH := %PATH% 11 | echo TMP := %TMP% 12 | echo FRAMEWORKDIR := %FRAMEWORKDIR% 13 | echo FRAMEWORKDIR64 := %FRAMEWORKDIR64% 14 | echo FRAMEWORKVERSION := %FRAMEWORKVERSION% 15 | echo FRAMEWORKVERSION64 := %FRAMEWORKVERSION64% 16 | echo UCRTCONTEXTROOT := %UCRTCONTEXTROOT% 17 | echo UCRTVERSION := %UCRTVERSION% 18 | echo UNIVERSALCRTSDKDIR := %UNIVERSALCRTSDKDIR% 19 | echo VCINSTALLDIR := %VCINSTALLDIR% 20 | echo VCTARGETSPATH := %VCTARGETSPATH% 21 | echo WINDOWSLIBPATH := %WINDOWSLIBPATH% 22 | echo WINDOWSSDKDIR := %WINDOWSSDKDIR% 23 | echo WINDOWSSDKLIBVERSION := %WINDOWSSDKLIBVERSION% 24 | echo WINDOWSSDKVERSION := %WINDOWSSDKVERSION% -------------------------------------------------------------------------------- /src/client/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(rat_client) 2 | 3 | add_library(${PROJECT_NAME} 4 | dummy.cpp 5 | ) 6 | add_custom_command( 7 | TARGET rat_client 8 | POST_BUILD 9 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 10 | COMMAND npm install 11 | ) 12 | -------------------------------------------------------------------------------- /src/client/dummy.cpp: -------------------------------------------------------------------------------- 1 | // Dummy file to get cmake to generate a project!! -------------------------------------------------------------------------------- /src/client/main.ts: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow } = require('electron') 2 | const path = require('path') 3 | const url = require('url') 4 | 5 | 6 | let win, serve; 7 | const args = process.argv.slice(1); 8 | serve = args.some(val => val === '--serve'); 9 | 10 | if (serve) { 11 | require('electron-reload')(__dirname, { 12 | }); 13 | } 14 | function createWindow() { 15 | // Create the browser window. 16 | win = new BrowserWindow({ width: 800, height: 600 }) 17 | 18 | // and load the index.html of the app. 19 | win.loadURL(url.format({ 20 | pathname: path.join(__dirname, 'index.html'), 21 | protocol: 'file:', 22 | slashes: true 23 | })) 24 | 25 | // Open the DevTools. 26 | if (serve) { 27 | win.webContents.openDevTools(); 28 | } 29 | 30 | // Emitted when the window is closed. 31 | win.on('closed', () => { 32 | // Dereference the window object, usually you would store windows 33 | // in an array if your app supports multi windows, this is the time 34 | // when you should delete the corresponding element. 35 | win = null 36 | }) 37 | } 38 | 39 | // This method will be called when Electron has finished 40 | // initialization and is ready to create browser windows. 41 | // Some APIs can only be used after this event occurs. 42 | app.on('ready', createWindow) 43 | 44 | // Quit when all windows are closed. 45 | app.on('window-all-closed', () => { 46 | // On macOS it is common for applications and their menu bar 47 | // to stay active until the user quits explicitly with Cmd + Q 48 | if (process.platform !== 'darwin') { 49 | app.quit() 50 | } 51 | }) 52 | 53 | app.on('activate', () => { 54 | // On macOS it's common to re-create a window in the app when the 55 | // dock icon is clicked and there are no other windows open. 56 | if (win === null) { 57 | createWindow() 58 | } 59 | }) 60 | -------------------------------------------------------------------------------- /src/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ratclient", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "private": true, 6 | "main": "main.js", 7 | "scripts": { 8 | "start": "npm-run-all --parallel webpack:watch electron:serve", 9 | "webpack:watch": "webpack --watch", 10 | "start:web": "webpack-dev-server --content-base . --port 4200 --inline", 11 | "build:electron:main": "tsc main.ts --outDir dist && copyfiles package.json dist && cd dist && npm install --prod && cd ..", 12 | "build": "webpack --display-error-details && npm run build:electron:main", 13 | "build:prod": "cross-env NODE_ENV=production npm run build", 14 | "electron:serve": "npm run build:electron:main && electron ./dist --serve", 15 | "electron:test": "electron ./dist", 16 | "electron:dev": "npm run build && electron ./dist", 17 | "electron:prod": "npm run build:prod && electron ./dist", 18 | "electron:linux": "npm run build:prod && node package.js --asar --platform=linux --arch=x64", 19 | "electron:windows": "npm run build:prod && node package.js --asar --platform=win32 --arch=ia32", 20 | "electron:mac": "npm run build:prod && node package.js --asar --platform=darwin --arch=x64" 21 | }, 22 | "dependencies": { 23 | "@angular/animations": "5.0.1", 24 | "@angular/cdk": "5.0.0-rc0", 25 | "@angular/common": "5.0.1", 26 | "@angular/compiler": "5.0.1", 27 | "@angular/core": "5.0.1", 28 | "@angular/forms": "5.0.1", 29 | "@angular/http": "5.0.1", 30 | "@angular/material": "5.0.0-rc0", 31 | "@angular/platform-browser": "5.0.1", 32 | "@angular/platform-browser-dynamic": "5.0.1", 33 | "@angular/router": "5.0.1", 34 | "core-js": "^2.4.1", 35 | "rxjs": "^5.5.2", 36 | "zone.js": "^0.8.14" 37 | }, 38 | "devDependencies": { 39 | "@angular/cli": "^1.5.0", 40 | "@angular/compiler-cli": "5.0.1", 41 | "@types/core-js": "0.9.36", 42 | "@types/jasmine": "2.5.54", 43 | "@types/node": "7.0.7", 44 | "autoprefixer": "7.1.4", 45 | "codelyzer": "3.2.0", 46 | "copyfiles": "1.2.0", 47 | "cross-env": "5.0.5", 48 | "css-loader": "0.28.7", 49 | "cssnano": "3.10.0", 50 | "electron": "1.7.11", 51 | "electron-packager": "9.1.0", 52 | "electron-reload": "1.2.1", 53 | "exports-loader": "0.6.4", 54 | "file-loader": "0.11.2", 55 | "html-loader": "0.5.1", 56 | "istanbul-instrumenter-loader": "3.0.0", 57 | "jasmine-core": "2.8.0", 58 | "jasmine-spec-reporter": "4.2.1", 59 | "json-loader": "0.5.7", 60 | "less-loader": "4.0.5", 61 | "minimist": "1.2.0", 62 | "mkdirp": "0.5.1", 63 | "npm-run-all": "^4.1.1", 64 | "postcss-loader": "2.0.6", 65 | "postcss-url": "7.1.2", 66 | "protractor": "5.1.2", 67 | "raw-loader": "0.5.1", 68 | "sass-loader": "6.0.6", 69 | "script-loader": "0.7.1", 70 | "source-map-loader": "0.2.1", 71 | "style-loader": "0.18.2", 72 | "stylus-loader": "3.0.1", 73 | "ts-node": "3.3.0", 74 | "tslint": "5.7.0", 75 | "typescript": "2.5.2", 76 | "url-loader": "^0.6.2", 77 | "webdriver-manager": "12.0.6", 78 | "webpack": "~3.8.1", 79 | "webpack-dev-server": "~2.9.3" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/client/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; -------------------------------------------------------------------------------- /src/client/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .OptionsButtonHidden{ 2 | position:relative; 3 | width:200px; 4 | margin:auto; 5 | z-index:50; 6 | } 7 | .connect-spinner { 8 | width:320px; 9 | margin:auto; 10 | } -------------------------------------------------------------------------------- /src/client/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 |
7 |
8 | 9 | 10 | 11 |
12 |
13 |

Connecting to {{ConnectedTo}}

14 | 15 |
16 | 17 | -------------------------------------------------------------------------------- /src/client/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { ConvertToKeyCode } from './lib/input_lite'; 2 | import { window } from 'rxjs/operators'; 3 | import { MonitorsCanvasComponent } from './monitorcanvas/monitorcanvas.component'; 4 | import { OptionsDialog } from './options.dialog/options.dialog'; 5 | import {Component, ElementRef, HostListener, OnInit, ViewChild, QueryList, ViewChildren} from '@angular/core'; 6 | import {MatDialog} from '@angular/material'; 7 | 8 | import {ConnectDialog} from './connect.dialog/connect.dialog'; 9 | import { 10 | ClientSettings, 11 | CreateClientDriverConfiguration, 12 | IClientDriver, 13 | Monitor, 14 | Point, 15 | Rect, 16 | WSMessage, 17 | } from './lib/rat_lite'; 18 | import {ConnectModel} from './models/connect.model'; 19 | 20 | @Component({selector : 'app-root', templateUrl : './app.component.html', styleUrls : [ './app.component.css' ]}) 21 | export class AppComponent implements OnInit { 22 | @ViewChild('HTMLCanvasMouseImage_') HTMLCanvasMouseImage_: ElementRef; 23 | @ViewChildren('HTMLCanvasScreenImage_') MonitorCanvas: QueryList; 24 | 25 | ScaleImage_ = false; 26 | ClientDriver_: IClientDriver; 27 | Socket_: WebSocket; 28 | Monitors = new Array(); 29 | MouseOverMonitor: Monitor; 30 | ClientSettings_ : ClientSettings; 31 | Cursor_: ImageData; 32 | width = '1000px'; 33 | ConnectedTo = ''; 34 | 35 | constructor(public dialog: MatDialog) {} 36 | public ngOnInit(): void 37 | { 38 | setTimeout(() => { this.OpenDialog(); }, 100); 39 | 40 | } 41 | public Disconnect(): void 42 | { 43 | if (this.Socket_) { 44 | this.Socket_.close(1000); 45 | } 46 | } 47 | public toggleoptions(): void{ 48 | this.dialog.open(OptionsDialog, { 49 | data: 50 | { 51 | ClientSettings:this.ClientSettings_, 52 | Monitors: this.Monitors 53 | } 54 | }).afterClosed().subscribe((a: ClientSettings)=>{ 55 | if(a){ 56 | this.ClientSettings_ = a; 57 | this.ClientDriver_.SendClientSettingsChanged(a); 58 | } 59 | }); 60 | } 61 | public overmonitor(mon: Monitor): void{ 62 | this.MouseOverMonitor=mon; 63 | console.log('overmonitor'); 64 | } 65 | private humanFileSize(bytes: number) : string { 66 | var thresh = 1024; 67 | if(Math.abs(bytes) < thresh) { 68 | return bytes + ' B'; 69 | } 70 | var units =['kB','MB','GB','TB','PB','EB','ZB','YB'] ; 71 | var u = -1; 72 | do { 73 | bytes /= thresh; 74 | ++u; 75 | } while(Math.abs(bytes) >= thresh && u < units.length - 1); 76 | return bytes.toFixed(1)+' '+units[u]; 77 | } 78 | public OpenDialog(): void 79 | { 80 | this.dialog.open(ConnectDialog, {disableClose : true}).afterClosed().subscribe((a: ConnectModel) => { 81 | if (a) { 82 | this.Socket_ = new WebSocket(a.Protocol + "://" + a.Host + ":" + a.Port); 83 | this.Socket_.binaryType = 'arraybuffer'; 84 | this.ConnectedTo = a.Protocol + "://" + a.Host + ":" + a.Port; 85 | this.ClientDriver_ = 86 | CreateClientDriverConfiguration() 87 | .onConnection((ws: WebSocket, ev: Event) => { 88 | console.log('onConnection'); 89 | this.ClientSettings_ = new ClientSettings(); 90 | }) 91 | .onBytesPerSecond((bytespersecond: number)=>{ 92 | document.title = 'Connected to '+ this.ConnectedTo + ' ' + this.humanFileSize(bytespersecond) + '/sec'; 93 | }) 94 | .onMessage((ws: WebSocket, message: WSMessage) => { console.log('onMessage length:' + message.data.byteLength); }) 95 | .onDisconnection((ws: WebSocket, code: number, message: string) => { 96 | console.log('onDisconnection ' + code + " "+ message); 97 | this.ConnectedTo =''; 98 | this.Cursor_ = null; 99 | this.ScaleImage_ = false; 100 | this.Monitors = new Array(); 101 | this.ClientDriver_ = null; 102 | this.Socket_ = null; 103 | this.MouseOverMonitor=null; 104 | this.ClientSettings_ = new ClientSettings(); 105 | this.OpenDialog(); 106 | }) 107 | .onClipboardChanged((clipstring: string) => { console.log('onClipboardChanged: ' + clipstring); }) 108 | .onFrameChanged((image: HTMLImageElement, monitor: Monitor, rect: Rect) => { 109 | this.MonitorCanvas.forEach((a: MonitorsCanvasComponent)=>{ 110 | a.onFrameChanged(image, monitor, rect); 111 | }); 112 | var totalwidth=0; 113 | this.MonitorCanvas.forEach((a: MonitorsCanvasComponent)=>{ totalwidth += (a.getScalingFactor() * a.Monitor.Width); }); 114 | this.width = totalwidth.toString() + 'px'; 115 | }) 116 | .onMonitorsChanged((monitors: Monitor[]) => { 117 | if(this.ClientSettings_.MonitorsToWatch.length ==0){ 118 | this.ClientSettings_.MonitorsToWatch = monitors; 119 | } 120 | this.Monitors = monitors; 121 | }) 122 | .onMouseImageChanged((image: ImageData) => { 123 | this.Cursor_ = image; 124 | if (this.HTMLCanvasMouseImage_ && this.HTMLCanvasMouseImage_.nativeElement ) { 125 | this.HTMLCanvasMouseImage_.nativeElement.getContext("2d").putImageData(this.Cursor_, 0, 0); 126 | } 127 | }) 128 | .onMousePositionChanged((pos: Point) => { 129 | if (this.HTMLCanvasMouseImage_ && this.HTMLCanvasMouseImage_.nativeElement ) { 130 | this.HTMLCanvasMouseImage_.nativeElement.style.top = pos.Y + "px"; 131 | this.HTMLCanvasMouseImage_.nativeElement.style.left = pos.X + "px"; 132 | } 133 | }) 134 | .onNewFrame((image: HTMLImageElement, monitor: Monitor, rect: Rect) => { 135 | this.MonitorCanvas.forEach((a: MonitorsCanvasComponent)=>{ 136 | a.onNewFrame(image, monitor, rect); 137 | }); 138 | }) 139 | .Build(this.Socket_); 140 | 141 | this.Socket_.onerror = (ev: Event) => { console.log(ev); }; 142 | } 143 | else { 144 | this.OpenDialog(); 145 | } 146 | }); 147 | } 148 | @HostListener('document:keydown', [ '$event' ]) 149 | onkeydown(ev: KeyboardEvent) 150 | { 151 | if (this.ClientDriver_ && this.Socket_.readyState === this.Socket_.OPEN) { 152 | this.ClientDriver_.SendKeyDown(ConvertToKeyCode(ev)); 153 | } 154 | } 155 | @HostListener('document:keyup', [ '$event' ]) 156 | onkeyup(ev: KeyboardEvent) 157 | { 158 | if (this.ClientDriver_ && this.Socket_.readyState === this.Socket_.OPEN) { 159 | this.ClientDriver_.SendKeyUp(ConvertToKeyCode(ev)); 160 | } 161 | } 162 | @HostListener('mousewheel', [ '$event' ]) 163 | onwheel(ev: WheelEvent) 164 | { 165 | if (this.ClientDriver_ && this.Socket_.readyState === this.Socket_.OPEN) { 166 | this.ClientDriver_.SendMouseScroll(ev.deltaY < 0 ? -1 : 1); 167 | } 168 | } 169 | @HostListener('mousemove', [ '$event' ]) 170 | onmove(ev: WheelEvent) 171 | { 172 | if (this.ClientDriver_ && this.Socket_.readyState === this.Socket_.OPEN && this.MouseOverMonitor) { 173 | let scaling = this.MouseOverMonitor.Scaling; 174 | let point = {Y : ev.pageY * scaling, X : ev.pageX* scaling} as Point; 175 | this.ClientDriver_.SendMousePosition(point); 176 | } 177 | } 178 | @HostListener('mouseup', [ '$event' ]) 179 | onmouseup(ev: MouseEvent) 180 | { 181 | if (this.ClientDriver_ && this.Socket_.readyState === this.Socket_.OPEN) { 182 | this.ClientDriver_.SendMouseUp(ev.button); 183 | } 184 | } 185 | @HostListener('onmousedownheel', [ '$event' ]) 186 | onmousedown(ev: MouseEvent) 187 | { 188 | if (this.ClientDriver_ && this.Socket_.readyState === this.Socket_.OPEN) { 189 | this.ClientDriver_.SendMouseDown(ev.button); 190 | } 191 | } 192 | @HostListener('mousedown', [ '$event' ]) 193 | mousedown(ev: MouseEvent) 194 | { 195 | if (this.ClientDriver_ && this.Socket_.readyState === this.Socket_.OPEN) { 196 | this.ClientDriver_.SendMouseDown(ev.button); 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/client/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { OptionsDialog } from './options.dialog/options.dialog'; 2 | import { NoopAnimationsModule } from '@angular/platform-browser/animations'; 3 | import { MaterialModule } from './material.module'; 4 | import { BrowserModule } from '@angular/platform-browser'; 5 | import { NgModule } from '@angular/core'; 6 | 7 | import { AppComponent } from './app.component'; 8 | import { ConnectDialog } from './connect.dialog/connect.dialog'; 9 | import { ReactiveFormsModule, FormsModule } from '@angular/forms'; 10 | import { MonitorsToWatchComponent } from './options.dialog/monitorstowatch.component'; 11 | import { MonitorsCanvasComponent } from './monitorcanvas/monitorcanvas.component'; 12 | 13 | @NgModule({ 14 | declarations: [ 15 | AppComponent, 16 | ConnectDialog, 17 | OptionsDialog, 18 | MonitorsToWatchComponent, 19 | MonitorsCanvasComponent 20 | ], 21 | imports: [ 22 | BrowserModule, 23 | MaterialModule, 24 | NoopAnimationsModule, 25 | ReactiveFormsModule, 26 | FormsModule 27 | ], 28 | providers: [], 29 | entryComponents:[ 30 | ConnectDialog, 31 | OptionsDialog 32 | ], 33 | bootstrap: [AppComponent] 34 | }) 35 | export class AppModule { } 36 | -------------------------------------------------------------------------------- /src/client/src/app/connect.dialog/connect.dialog.html: -------------------------------------------------------------------------------- 1 |

Connect to Host

2 | 3 |
4 | 5 | 6 | 13 | 22 | 32 | 33 |
7 | 8 | 9 | {{ protocol }} 10 | 11 | 12 | 14 | 15 | 16 | 17 | Host is 18 | required 19 | 20 | 21 | 23 | 24 | 25 | {{port.value.length}} / 5 26 | 27 | Port is 28 | required 29 | 30 | 31 |
34 | 35 |
36 |
-------------------------------------------------------------------------------- /src/client/src/app/connect.dialog/connect.dialog.ts: -------------------------------------------------------------------------------- 1 | import {Component, Inject, OnInit} from '@angular/core'; 2 | import {FormBuilder, FormGroup, Validators} from '@angular/forms'; 3 | import {MatDialogRef} from '@angular/material'; 4 | 5 | import {ValidateNumericOnly} from '../validators/numericonly'; 6 | import { ConnectModel } from '../models/connect.model'; 7 | 8 | @Component({templateUrl : './connect.dialog.html'}) 9 | export class ConnectDialog implements OnInit { 10 | public f: FormGroup; 11 | submitting=false; 12 | public Protocols = ['ws' 13 | // , 'wss' 14 | ]; 15 | constructor(public dialogRef: MatDialogRef, private fb: FormBuilder) {} 16 | public ngOnInit(): void 17 | { 18 | var defaltproto = 'ws'; 19 | //if (window.location.protocol == "https:") { 20 | // defaltproto= "wss"; 21 | // } 22 | this.f = this.fb.group({ 23 | 'Protocol' : [ 24 | defaltproto, [ Validators.required ] 25 | ], 26 | 'Host' : [ 27 | 'localhost', [ Validators.required, Validators.minLength(2) ] 28 | ], 29 | 'Port' : 30 | [ '6001', [ Validators.required, Validators.minLength(2), ValidateNumericOnly ] ] 31 | }); 32 | } 33 | public onSubmit(): void { 34 | if(!this.submitting && this.f.valid){ 35 | this.submitting = true; 36 | this.dialogRef.close(this.f.value as ConnectModel); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/client/src/app/lib/input_lite.ts: -------------------------------------------------------------------------------- 1 |  2 | 3 | export enum KeyCodes { 4 | KEY_A = 4, 5 | KEY_B = 5, 6 | KEY_C = 6, 7 | KEY_D = 7, 8 | KEY_E = 8, 9 | KEY_F = 9, 10 | KEY_G = 10, 11 | KEY_H = 11, 12 | KEY_I = 12, 13 | KEY_J = 13, 14 | KEY_K = 14, 15 | KEY_L = 15, 16 | KEY_M = 16, 17 | KEY_N = 17, 18 | KEY_O = 18, 19 | KEY_P = 19, 20 | KEY_Q = 20, 21 | KEY_R = 21, 22 | KEY_S = 22, 23 | KEY_T = 23, 24 | KEY_U = 24, 25 | KEY_V = 25, 26 | KEY_W = 26, 27 | KEY_X = 27, 28 | KEY_Y = 28, 29 | KEY_Z = 29, 30 | KEY_1 = 30, 31 | KEY_2 = 31, 32 | KEY_3 = 32, 33 | KEY_4 = 33, 34 | KEY_5 = 34, 35 | KEY_6 = 35, 36 | KEY_7 = 36, 37 | KEY_8 = 37, 38 | KEY_9 = 38, 39 | KEY_0 = 39, 40 | KEY_Enter = 40, 41 | KEY_Escape = 41, 42 | KEY_Backspace = 42, 43 | KEY_Tab = 43, 44 | KEY_Space = 44, 45 | KEY_Minus = 45, 46 | KEY_Equals = 46, 47 | KEY_LeftBracket = 47, 48 | KEY_RightBracket = 48, 49 | KEY_Backslash = 49, 50 | KEY_Semicolon = 51, 51 | KEY_Quote = 52, 52 | KEY_Grave = 53, 53 | KEY_Comma = 54, 54 | KEY_Period = 55, 55 | KEY_Slash = 56, 56 | KEY_CapsLock = 57, 57 | KEY_F1 = 58, 58 | KEY_F2 = 59, 59 | KEY_F3 = 60, 60 | KEY_F4 = 61, 61 | KEY_F5 = 62, 62 | KEY_F6 = 63, 63 | KEY_F7 = 64, 64 | KEY_F8 = 65, 65 | KEY_F9 = 66, 66 | KEY_F10 = 67, 67 | KEY_F11 = 68, 68 | KEY_F12 = 69, 69 | KEY_PrintScreen = 70, 70 | KEY_ScrollLock = 71, 71 | KEY_Pause = 72, 72 | KEY_Insert = 73, 73 | KEY_Home = 74, 74 | KEY_PageUp = 75, 75 | KEY_Delete = 76, 76 | KEY_End = 77, 77 | KEY_PageDown = 78, 78 | KEY_Right = 79, 79 | KEY_Left = 80, 80 | KEY_Down = 81, 81 | KEY_Up = 82, 82 | KP_NumLock = 83, 83 | KP_Divide = 84, 84 | KP_Multiply = 85, 85 | KP_Subtract = 86, 86 | KP_Add = 87, 87 | KP_Enter = 88, 88 | KP_1 = 89, 89 | KP_2 = 90, 90 | KP_3 = 91, 91 | KP_4 = 92, 92 | KP_5 = 93, 93 | KP_6 = 94, 94 | KP_7 = 95, 95 | KP_8 = 96, 96 | KP_9 = 97, 97 | KP_0 = 98, 98 | KP_Point = 99, 99 | KEY_NonUSBackslash = 100, 100 | KP_Equals = 103, 101 | KEY_F13 = 104, 102 | KEY_F14 = 105, 103 | KEY_F15 = 106, 104 | KEY_F16 = 107, 105 | KEY_F17 = 108, 106 | KEY_F18 = 109, 107 | KEY_F19 = 110, 108 | KEY_F20 = 111, 109 | KEY_F21 = 112, 110 | KEY_F22 = 113, 111 | KEY_F23 = 114, 112 | KEY_F24 = 115, 113 | KEY_Help = 117, 114 | KEY_Menu = 118, 115 | KEY_LeftControl = 224, 116 | KEY_LeftShift = 225, 117 | KEY_LeftAlt = 226, 118 | KEY_LeftMeta = 227, 119 | KEY_RightControl = 228, 120 | KEY_RightShift = 229, 121 | KEY_RightAlt = 230, 122 | KEY_RightMeta = 231, 123 | INVALID = 255 124 | }; 125 | 126 | export function ConvertToKeyCode(key: KeyboardEvent): KeyCodes { 127 | 128 | switch (key.key) { 129 | case "0": 130 | return KeyCodes.KEY_0; 131 | case "1": 132 | case "2": 133 | case "3": 134 | case "4": 135 | case "5": 136 | case "6": 137 | case "7": 138 | case "8": 139 | case "9": 140 | return KeyCodes.KEY_1 + (key.key.charCodeAt(0) - 1); 141 | case "A": 142 | case "B": 143 | case "C": 144 | case "D": 145 | case "E": 146 | case "F": 147 | case "G": 148 | case "H": 149 | case "I": 150 | case "J": 151 | case "K": 152 | case "L": 153 | case "M": 154 | case "N": 155 | case "O": 156 | case "P": 157 | case "Q": 158 | case "R": 159 | case "S": 160 | case "T": 161 | case "U": 162 | case "V": 163 | case "W": 164 | case "X": 165 | case "Y": 166 | case "Z": 167 | return KeyCodes.KEY_A + (+key.key - +"A"); 168 | case "a": 169 | case "b": 170 | case "c": 171 | case "d": 172 | case "e": 173 | case "f": 174 | case "g": 175 | case "h": 176 | case "i": 177 | case "j": 178 | case "k": 179 | case "l": 180 | case "m": 181 | case "n": 182 | case "o": 183 | case "p": 184 | case "q": 185 | case "r": 186 | case "s": 187 | case "t": 188 | case "u": 189 | case "v": 190 | case "w": 191 | case "x": 192 | case "y": 193 | case "z": 194 | return KeyCodes.KEY_A + (key.key.charCodeAt(0) - "a".charCodeAt(0)); 195 | case "Enter": 196 | return KeyCodes.KEY_Enter; 197 | case "Escape": 198 | return KeyCodes.KEY_Escape; 199 | case "Backspace": 200 | return KeyCodes.KEY_Backspace; 201 | case "Tab": 202 | return KeyCodes.KEY_Tab; 203 | case " ": 204 | return KeyCodes.KEY_Space; 205 | case "-": 206 | return KeyCodes.KEY_Minus; 207 | case "=": 208 | return KeyCodes.KEY_Equals; // this is correct and not a mistype 209 | case "[": 210 | return KeyCodes.KEY_LeftBracket; 211 | case "[": 212 | return KeyCodes.KEY_RightBracket; 213 | case "\\": 214 | return KeyCodes.KEY_Backslash; 215 | case ";": 216 | return KeyCodes.KEY_Semicolon; 217 | case "'": 218 | return KeyCodes.KEY_Quote; 219 | case "`": 220 | return KeyCodes.KEY_Grave; 221 | case ",": 222 | return KeyCodes.KEY_Comma; 223 | case ".": 224 | return KeyCodes.KEY_Period; 225 | case "/": 226 | return KeyCodes.KEY_Slash; 227 | case "CapsLock": 228 | return KeyCodes.KEY_CapsLock; 229 | case "F1": 230 | return KeyCodes.KEY_F1; 231 | case "F2": 232 | return KeyCodes.KEY_F2; 233 | case "F3": 234 | return KeyCodes.KEY_F3; 235 | case "F4": 236 | return KeyCodes.KEY_F4; 237 | case "F5": 238 | return KeyCodes.KEY_F5; 239 | case "F6": 240 | return KeyCodes.KEY_F6; 241 | case "F7": 242 | return KeyCodes.KEY_F7; 243 | case "F8": 244 | return KeyCodes.KEY_F8; 245 | case "F9": 246 | return KeyCodes.KEY_F9; 247 | case "F10": 248 | return KeyCodes.KEY_F10; 249 | case "F11": 250 | return KeyCodes.KEY_F11; 251 | case "F12": 252 | return KeyCodes.KEY_F12; 253 | case "F13": 254 | return KeyCodes.KEY_F13; 255 | case "F14": 256 | return KeyCodes.KEY_F14; 257 | case "F15": 258 | return KeyCodes.KEY_F15; 259 | case "F16": 260 | return KeyCodes.KEY_F1; 261 | case "F17": 262 | return KeyCodes.KEY_F1; 263 | case "F18": 264 | return KeyCodes.KEY_F1; 265 | case "F19": 266 | return KeyCodes.KEY_F1; 267 | case "F20": 268 | return KeyCodes.KEY_F1; 269 | case "Print": 270 | return KeyCodes.KEY_PrintScreen; 271 | case "ScrollLock": 272 | return KeyCodes.KEY_ScrollLock; 273 | case "Pause": 274 | return KeyCodes.KEY_Pause; 275 | case "Insert": 276 | return KeyCodes.KEY_Insert; 277 | case "Home": 278 | return KeyCodes.KEY_Home; 279 | case "PageUp": 280 | return KeyCodes.KEY_PageUp; 281 | case "Delete": 282 | return KeyCodes.KEY_Delete; 283 | case "End": 284 | return KeyCodes.KEY_End; 285 | case "PageDown": 286 | return KeyCodes.KEY_PageDown; 287 | case "ArrowRight": 288 | return KeyCodes.KEY_Right; 289 | case "ArrowLeft": 290 | return KeyCodes.KEY_Left; 291 | case "ArrowDown": 292 | return KeyCodes.KEY_Down; 293 | case "ArrowUp": 294 | return KeyCodes.KEY_Up; 295 | case "NumLock": 296 | return KeyCodes.KP_NumLock; 297 | case "/": 298 | return KeyCodes.KP_Divide; 299 | case "*": 300 | return KeyCodes.KP_Multiply; 301 | case "-": 302 | return KeyCodes.KP_Subtract; 303 | case "+": 304 | return KeyCodes.KP_Add; 305 | case ".": 306 | return KeyCodes.KP_Point; 307 | case "Help": 308 | return KeyCodes.KEY_Help; 309 | case "Alt": 310 | return KeyCodes.KEY_Menu; 311 | case "Control": 312 | return KeyCodes.KEY_LeftControl; 313 | case "Shift": 314 | return KeyCodes.KEY_LeftShift; 315 | case "Meta": 316 | return KeyCodes.KEY_LeftMeta; 317 | default: 318 | return KeyCodes.INVALID; 319 | } 320 | 321 | } 322 | 323 | export enum MouseButtons { LEFT, MIDDLE, RIGHT }; 324 | 325 | export class KeyEvent { 326 | Pressed: boolean; 327 | Key: KeyCodes; 328 | }; 329 | export class MouseButtonEvent { 330 | Pressed: boolean; 331 | Button: MouseButtons; 332 | }; 333 | export class MouseScrollEvent { 334 | Offset = 0; 335 | }; 336 | export class MousePositionOffsetEvent { 337 | X = 0; 338 | Y = 0; 339 | }; 340 | export class MousePositionAbsoluteEvent { 341 | X = 0; 342 | Y = 0; 343 | }; -------------------------------------------------------------------------------- /src/client/src/app/lib/rat_lite.ts: -------------------------------------------------------------------------------- 1 | import { KeyCodes, MouseButtons } from './input_lite'; 2 | export enum OpCode { 3 | CONTINUATION = 0, 4 | TEXT = 1, 5 | BINARY = 2, 6 | CLOSE = 8, 7 | PING = 9, 8 | PONG = 10, 9 | INVALID = 255 10 | } 11 | ; 12 | 13 | export enum ImageEncoding { 14 | COLOR, 15 | GRAYSCALE 16 | } 17 | ; 18 | export enum ClipboardSharing { 19 | NOT_SHARED, 20 | SHARED 21 | } 22 | ; 23 | export class WSMessage { 24 | data: DataView; 25 | code: OpCode; 26 | }; 27 | export class Point { 28 | X: number; 29 | Y: number; 30 | }; 31 | export class Rect { 32 | Origin: Point; 33 | Height: number; 34 | Width: number; 35 | }; 36 | export class Monitor { 37 | Id: number; 38 | Index: number; 39 | Height: number; 40 | Width: number; 41 | // Offsets are the number of pixels that a monitor can be from the origin. For example, users can shuffle their 42 | // monitors around so this affects their offset. 43 | OffsetX: number; 44 | OffsetY: number; 45 | Name: string; 46 | Scaling: number; 47 | }; 48 | export class ClientSettings { 49 | ShareClip = ClipboardSharing.NOT_SHARED; 50 | ImageCompressionSetting = 70; 51 | EncodeImagesAsGrayScale = ImageEncoding.COLOR; 52 | MonitorsToWatch = new Array(); 53 | }; 54 | export enum PACKET_TYPES { 55 | INVALID, 56 | HTTP_MSG, 57 | ONMONITORSCHANGED, 58 | ONFRAMECHANGED, 59 | ONNEWFRAME, 60 | ONMOUSEIMAGECHANGED, 61 | ONMOUSEPOSITIONCHANGED, 62 | ONKEYUP, 63 | ONKEYDOWN, 64 | ONMOUSEUP, 65 | ONMOUSEDOWN, 66 | ONMOUSESCROLL, 67 | ONCLIPBOARDTEXTCHANGED, 68 | ONCLIENTSETTINGSCHANGED, 69 | // use LAST_PACKET_TYPE as the starting point of your custom packet types. Everything before this is used internally by the library 70 | LAST_PACKET_TYPE 71 | } 72 | ; 73 | 74 | export class IClientDriver { 75 | protected ShareClip = false; 76 | protected Monitors = new Array(); 77 | protected WebSocket_: WebSocket; 78 | 79 | protected onConnection_: (ws: WebSocket, ev: Event) => void; 80 | protected onMessage_: (ws: WebSocket, message: WSMessage) => void; 81 | protected onDisconnection_: (ws: WebSocket, code: number, message: string) => void; 82 | protected onMonitorsChanged_: (monitors: Monitor[]) => void; 83 | protected onFrameChanged_: (image: HTMLImageElement, monitor: Monitor, rect: Rect) => void; 84 | protected onNewFrame_: (image: HTMLImageElement, monitor: Monitor, rect: Rect) => void; 85 | protected onMouseImageChanged_: (image: ImageData) => void; 86 | protected onMousePositionChanged_: (point: Point) => void; 87 | protected onClipboardChanged_: (clipstring: string) => void; 88 | protected onBytesPerSecondChanged: (bytespersecond: number) => void; 89 | 90 | protected ConnectedToSelf_ = false; 91 | protected BytesPerSecond = 0; 92 | protected SecondTimer = 0; 93 | 94 | setShareClipboard(share: boolean): void { this.ShareClip = share; } 95 | getShareClipboard(): boolean { return this.ShareClip; } 96 | SendKeyUp(key: KeyCodes): void 97 | { 98 | if (this.ConnectedToSelf_) 99 | return; 100 | var data = new Uint8Array(4 + 1); 101 | var dataview = new DataView(data.buffer); 102 | dataview.setUint32(0, PACKET_TYPES.ONKEYUP, true); 103 | dataview.setUint8(4, key); 104 | this.WebSocket_.send(data.buffer); 105 | } 106 | SendKeyDown(key: KeyCodes): void 107 | { 108 | if (this.ConnectedToSelf_) 109 | return; 110 | var data = new Uint8Array(4 + 1); 111 | var dataview = new DataView(data.buffer); 112 | dataview.setUint32(0, PACKET_TYPES.ONKEYDOWN, true); 113 | dataview.setUint8(4, key); 114 | this.WebSocket_.send(data.buffer); 115 | } 116 | SendMouseUp(button: MouseButtons): void 117 | { 118 | if (this.ConnectedToSelf_) 119 | return; 120 | var data = new Uint8Array(4 + 1); 121 | var dataview = new DataView(data.buffer); 122 | dataview.setUint32(0, PACKET_TYPES.ONMOUSEUP, true); 123 | dataview.setUint8(4, button); 124 | this.WebSocket_.send(data.buffer); 125 | } 126 | SendMouseDown(button: MouseButtons): void 127 | { 128 | if (this.ConnectedToSelf_) 129 | return; 130 | var data = new Uint8Array(4 + 1); 131 | var dataview = new DataView(data.buffer); 132 | dataview.setUint32(0, PACKET_TYPES.ONMOUSEDOWN, true); 133 | dataview.setUint8(4, button); 134 | this.WebSocket_.send(data.buffer); 135 | } 136 | SendMouseScroll(offset: number): void 137 | { 138 | if (this.ConnectedToSelf_) 139 | return; 140 | var data = new Uint8Array(4 + 4); 141 | var dataview = new DataView(data.buffer); 142 | dataview.setUint32(0, PACKET_TYPES.ONMOUSESCROLL, true); 143 | dataview.setUint32(4, offset, true); 144 | this.WebSocket_.send(data.buffer); 145 | } 146 | SendMousePosition(pos: Point): void 147 | { 148 | if (this.ConnectedToSelf_) 149 | return; 150 | var data = new Uint8Array(4 + 8); 151 | var dataview = new DataView(data.buffer); 152 | dataview.setUint32(0, PACKET_TYPES.ONMOUSEPOSITIONCHANGED, true); 153 | dataview.setInt32(4, pos.X, true); 154 | dataview.setInt32(8, pos.Y, true); 155 | this.WebSocket_.send(data.buffer); 156 | } 157 | SendClipboardChanged(text: string): void 158 | { 159 | if (this.ConnectedToSelf_) 160 | return; 161 | var data = new Uint8Array(4 + text.length); 162 | var dataview = new DataView(data.buffer); 163 | dataview.setUint32(0, PACKET_TYPES.ONMOUSESCROLL, true); 164 | for (var i = 0; i < text.length; i++) { 165 | data[4 + i] = text.charCodeAt(0); 166 | } 167 | this.WebSocket_.send(data.buffer); 168 | } 169 | SendClientSettingsChanged(clientsettings: ClientSettings): void 170 | { 171 | if (!clientsettings || !clientsettings.MonitorsToWatch || clientsettings.MonitorsToWatch.length <= 0) 172 | return; 173 | var beginsize = 1 + 4 + 1; 174 | var data = new Uint8Array(4 + beginsize + (4 * clientsettings.MonitorsToWatch.length)); 175 | var dataview = new DataView(data.buffer); 176 | var offset = 0; 177 | dataview.setUint32(offset, PACKET_TYPES.ONCLIENTSETTINGSCHANGED, true); 178 | offset += 4; 179 | dataview.setUint8(offset, clientsettings.ShareClip); 180 | offset += 1; 181 | dataview.setInt32(offset, clientsettings.ImageCompressionSetting, true); 182 | offset += 4; 183 | dataview.setUint8(offset, clientsettings.EncodeImagesAsGrayScale); 184 | offset += 1; 185 | for (var i = 0; i < clientsettings.MonitorsToWatch.length; i++) { 186 | dataview.setInt32(offset, clientsettings.MonitorsToWatch[i].Id, true); 187 | offset += 4; 188 | } 189 | this.WebSocket_.send(data.buffer); 190 | } 191 | }; 192 | 193 | export class IClientDriverConfiguration extends IClientDriver { 194 | onBytesPerSecond(callback: (bytespersecond: number) => void): IClientDriverConfiguration 195 | { 196 | this.onBytesPerSecondChanged = callback; 197 | return this; 198 | } 199 | onConnection(callback: (ws: WebSocket, ev: Event) => void): IClientDriverConfiguration 200 | { 201 | this.onConnection_ = callback; 202 | return this; 203 | } 204 | onMessage(callback: (ws: WebSocket, message: WSMessage) => void): IClientDriverConfiguration 205 | { 206 | this.onMessage_ = callback; 207 | return this; 208 | } 209 | onDisconnection(callback: (ws: WebSocket, code: number, message: string) => void): IClientDriverConfiguration 210 | { 211 | this.onDisconnection_ = callback; 212 | return this; 213 | } 214 | onMonitorsChanged(callback: (monitors: Monitor[]) => void): IClientDriverConfiguration 215 | { 216 | this.onMonitorsChanged_ = callback; 217 | return this; 218 | } 219 | onFrameChanged(callback: (image: HTMLImageElement, monitor: Monitor, rect: Rect) => void): IClientDriverConfiguration 220 | { 221 | this.onFrameChanged_ = callback; 222 | return this; 223 | } 224 | onNewFrame(callback: (image: HTMLImageElement, monitor: Monitor, rect: Rect) => void): IClientDriverConfiguration 225 | { 226 | this.onNewFrame_ = callback; 227 | return this; 228 | } 229 | onMouseImageChanged(callback: (image: ImageData) => void): IClientDriverConfiguration 230 | { 231 | this.onMouseImageChanged_ = callback; 232 | return this; 233 | } 234 | onMousePositionChanged(callback: (point: Point) => void): IClientDriverConfiguration 235 | { 236 | this.onMousePositionChanged_ = callback; 237 | return this; 238 | } 239 | onClipboardChanged(callback: (clipstring: string) => void): IClientDriverConfiguration 240 | { 241 | this.onClipboardChanged_ = callback; 242 | return this; 243 | } 244 | private _arrayBufferToBase64(buffer: Uint8Array): string 245 | { 246 | var binary = ''; 247 | for (var i = 0; i < buffer.byteLength; i++) { 248 | binary += String.fromCharCode(buffer[i]); 249 | } 250 | return window.btoa(binary); 251 | } 252 | private MonitorsChanged(ws: WebSocket, dataview: DataView) 253 | { 254 | 255 | if (!this.onMonitorsChanged_) 256 | return; 257 | let sizeofmonitor = 7 * 4 + 128; 258 | let num = dataview.byteLength / sizeofmonitor; 259 | 260 | if (dataview.byteLength == num * sizeofmonitor && num < 8) { 261 | this.Monitors = new Array(); 262 | let currentoffset = 0; 263 | for (var i = 0; i < num; i++) { 264 | currentoffset = i * sizeofmonitor; 265 | var name = ''; 266 | for (var j = 0, strLen = 128; j < strLen; j++) { 267 | var char = String.fromCharCode(dataview.getUint8((24 + j) + currentoffset)); 268 | if (char == '\0') { 269 | break; 270 | } 271 | name += char; 272 | } 273 | this.Monitors.push({ 274 | Id : dataview.getInt32(0 + currentoffset, true), 275 | Index : dataview.getInt32(4 + currentoffset, true), 276 | Height : dataview.getInt32(8 + currentoffset, true), 277 | Width : dataview.getInt32(12 + currentoffset, true), 278 | OffsetX : dataview.getInt32(16 + currentoffset, true), 279 | OffsetY : dataview.getInt32(20 + currentoffset, true), 280 | Name : name, 281 | Scaling : dataview.getFloat32(24 + 128 + currentoffset, true) 282 | }); 283 | } 284 | return this.onMonitorsChanged_(this.Monitors); 285 | } 286 | else if (dataview.byteLength == 0) { 287 | // it is possible to have no monitors.. shouldnt disconnect in that case 288 | return this.onMonitorsChanged_(this.Monitors); 289 | } 290 | if (this.onDisconnection_) { 291 | this.onDisconnection_(ws, 1000, "Invalid Monitor Count"); 292 | } 293 | ws.close(1000, "Invalid Monitor Count"); 294 | } 295 | 296 | private Frame(ws: WebSocket, dataview: DataView, callback: (image: HTMLImageElement, monitor: Monitor, rect: Rect) => void) 297 | { 298 | 299 | if (dataview.byteLength >= 4 * 4 + 4) { 300 | var monitorid = dataview.getInt32(0, true); 301 | var rect = { 302 | Origin : {X : dataview.getInt32(4, true), Y : dataview.getInt32(8, true)}, 303 | Height : dataview.getInt32(12, true), 304 | Width : dataview.getInt32(16, true) 305 | }; 306 | 307 | var foundmonitor = this.Monitors.filter(a => a.Id == monitorid); 308 | 309 | if (foundmonitor.length > 0) { 310 | var i = new Image(); 311 | i.src = "data:image/jpeg;base64," + this._arrayBufferToBase64(new Uint8Array(dataview.buffer, 20 + dataview.byteOffset)); 312 | i.onload = (ev: Event) => { callback(i, foundmonitor[0], rect); }; 313 | i.onerror = (ev: Event) => { console.log(ev); }; 314 | i.oninvalid = (ev: Event) => { console.log(ev); }; 315 | } 316 | return; 317 | } 318 | if (this.onDisconnection_) { 319 | this.onDisconnection_(ws, 1000, "Received invalid lenght on onMouseImageChanged"); 320 | } 321 | ws.close(1000, "Received invalid lenght on onMouseImageChanged"); 322 | } 323 | 324 | private MouseImageChanged(ws: WebSocket, dataview: DataView) 325 | { 326 | if (!this.onMouseImageChanged_) 327 | return; 328 | if (dataview.byteLength >= 4 * 4) { 329 | var rect = { 330 | Origin : {X : dataview.getInt32(0, true), Y : dataview.getInt32(4, true)}, 331 | Height : dataview.getInt32(8, true), 332 | Width : dataview.getInt32(12, true) 333 | }; 334 | 335 | var canvas = document.createElement('canvas'); 336 | var imageData = canvas.getContext('2d').createImageData(rect.Width, rect.Height); 337 | for (var i = 16; i < dataview.byteLength; i++) { 338 | imageData.data[i] = dataview[i]; 339 | } 340 | if (dataview.byteLength >= 4 * 4 + (rect.Width * rect.Height * 4)) { 341 | return this.onMouseImageChanged_(imageData); 342 | } 343 | } 344 | if (this.onDisconnection_) { 345 | this.onDisconnection_(ws, 1000, "Received invalid lenght on onMouseImageChanged"); 346 | } 347 | ws.close(1000, "Received invalid lenght on onMouseImageChanged"); 348 | } 349 | private MousePositionChanged(ws: WebSocket, dataview: DataView) 350 | { 351 | if (!this.onMousePositionChanged_) 352 | return; 353 | if (dataview.byteLength == 8) { 354 | var p = {X : dataview.getInt32(0, true), Y : dataview.getInt32(4, true)}; 355 | return this.onMousePositionChanged_(p); 356 | } 357 | if (this.onDisconnection_) { 358 | this.onDisconnection_(ws, 1000, "Received invalid lenght on onMousePositionChanged"); 359 | } 360 | ws.close(1000, "Received invalid lenght on onMousePositionChanged"); 361 | } 362 | private ClipboardTextChanged(dataview: DataView) 363 | { 364 | if (!this.ShareClip || !this.onClipboardChanged_) 365 | return; 366 | if (dataview.byteLength < 1024 * 100) { // 100K max 367 | var text = ''; 368 | for (var i = 0, strLen = 128; i < strLen; i++) { 369 | text += String.fromCharCode.apply(dataview.getUint8(20 + i)); 370 | } 371 | this.onClipboardChanged_(text); 372 | } 373 | } 374 | Build(ws: WebSocket): IClientDriver 375 | { 376 | this.WebSocket_ = ws; 377 | var self = this; 378 | ws.binaryType = 'arraybuffer'; 379 | this.ConnectedToSelf_ = ws.url.toLowerCase().indexOf('127.0.0.1') != -1 || ws.url.toLowerCase().indexOf('localhost') != -1 || 380 | ws.url.toLowerCase().indexOf('::1') != -1; 381 | this.SecondTimer = performance.now(); 382 | ws.onopen = (ev: Event) => { 383 | console.log('onopen'); 384 | if (self.onConnection_) { 385 | self.onConnection_(ws, ev); 386 | } 387 | }; 388 | ws.onclose = (ev: CloseEvent) => { 389 | console.log('onclose'); 390 | if (self.onDisconnection_) { 391 | self.onDisconnection_(ws, ev.code, ev.reason); 392 | } 393 | }; 394 | ws.onmessage = (ev: MessageEvent) => { 395 | 396 | var t0 = performance.now(); 397 | if (t0 - this.SecondTimer > 1000) { 398 | this.onBytesPerSecondChanged(this.BytesPerSecond); 399 | this.SecondTimer = t0; 400 | this.BytesPerSecond = 0; 401 | } 402 | var data = new DataView(ev.data); 403 | this.BytesPerSecond += data.byteLength; 404 | var packettype = data.getInt32(0, true); 405 | var self = this; 406 | // console.log('received: ' + packettype); 407 | switch (packettype) { 408 | case PACKET_TYPES.ONMONITORSCHANGED: 409 | this.MonitorsChanged(ws, new DataView(ev.data, 4)); 410 | break; 411 | case PACKET_TYPES.ONFRAMECHANGED: 412 | if (this.onFrameChanged_) { 413 | this.Frame(ws, new DataView(ev.data, 4), this.onFrameChanged_); 414 | } 415 | break; 416 | case PACKET_TYPES.ONNEWFRAME: 417 | if (this.onNewFrame_) { 418 | this.Frame(ws, new DataView(ev.data, 4), this.onNewFrame_); 419 | } 420 | break; 421 | case PACKET_TYPES.ONMOUSEIMAGECHANGED: 422 | this.MouseImageChanged(ws, new DataView(ev.data, 4)); 423 | break; 424 | case PACKET_TYPES.ONMOUSEPOSITIONCHANGED: 425 | this.MousePositionChanged(ws, new DataView(ev.data, 4)); 426 | break; 427 | case PACKET_TYPES.ONCLIPBOARDTEXTCHANGED: 428 | this.ClipboardTextChanged(new DataView(ev.data, 4)); 429 | break; 430 | default: 431 | if (this.onMessage_) { 432 | var r = new WSMessage(); 433 | r.data = new DataView(ev.data, 4); 434 | if (ev.data instanceof ArrayBuffer) { 435 | r.code = OpCode.BINARY; 436 | } 437 | else if (typeof ev.data === "string") { 438 | r.code = OpCode.TEXT; 439 | } 440 | this.onMessage_(ws, r); // pass up the chain 441 | } 442 | break; 443 | } 444 | var t1 = performance.now(); 445 | // console.log("took " + (t1 - t0) + " milliseconds to process the receive loop"); 446 | }; 447 | return this; 448 | } 449 | }; 450 | export function CreateClientDriverConfiguration(): IClientDriverConfiguration { return new IClientDriverConfiguration(); } 451 | -------------------------------------------------------------------------------- /src/client/src/app/material.module.ts: -------------------------------------------------------------------------------- 1 | import {MatButtonModule, MatCheckboxModule, MatDialogModule, MatInputModule, MatSelectModule, MatSidenavModule, MatListModule, MatProgressSpinnerModule} from '@angular/material'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | @NgModule({ 5 | imports: [ 6 | MatButtonModule, 7 | MatCheckboxModule, 8 | MatDialogModule, 9 | MatInputModule, 10 | MatSelectModule, 11 | MatSidenavModule, 12 | MatListModule, 13 | MatProgressSpinnerModule 14 | ], 15 | exports: [ 16 | MatButtonModule, 17 | MatCheckboxModule, 18 | MatDialogModule, 19 | MatInputModule, 20 | MatSelectModule, 21 | MatSidenavModule, 22 | MatListModule, 23 | MatProgressSpinnerModule 24 | ], 25 | }) 26 | export class MaterialModule { } -------------------------------------------------------------------------------- /src/client/src/app/models/connect.model.ts: -------------------------------------------------------------------------------- 1 | import { ClientSettings, Monitor } from '../lib/rat_lite'; 2 | export class ConnectModel{ 3 | public Protocol: string; 4 | public Host: string; 5 | public Port: number; 6 | } 7 | export class OptionsModel{ 8 | public ClientSettings: ClientSettings; 9 | public Monitors : Monitor[]; 10 | } -------------------------------------------------------------------------------- /src/client/src/app/monitorcanvas/monitorcanvas.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/client/src/app/monitorcanvas/monitorcanvas.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, ElementRef, Input, OnInit, ViewChild} from "@angular/core"; 2 | 3 | import {Monitor, Rect} from "../lib/rat_lite"; 4 | 5 | @Component({selector : 'monitor-canvas', templateUrl : './monitorcanvas.component.html'}) 6 | export class MonitorsCanvasComponent implements OnInit { 7 | @ViewChild('thiscanvas') ThisCanvas: ElementRef; 8 | @Input() Monitor: Monitor; 9 | 10 | ngOnInit(): void 11 | { 12 | if (this.ThisCanvas && this.ThisCanvas.nativeElement ) { 13 | this.ThisCanvas.nativeElement.width = this.getScalingFactor()*this.Monitor.Width; 14 | this.ThisCanvas.nativeElement.height =this.getScalingFactor()* this.Monitor.Height; 15 | } 16 | } 17 | public getScalingFactor(): number{ 18 | return (1 / this.Monitor.Scaling); 19 | } 20 | public onFrameChanged(image: HTMLImageElement, monitor: Monitor, rect: Rect): void 21 | { 22 | if (this.Monitor && this.ThisCanvas && this.ThisCanvas.nativeElement && this.Monitor.Id == monitor.Id) { 23 | this.ThisCanvas.nativeElement.getContext("2d").drawImage(image,this.getScalingFactor()* rect.Origin.X, this.getScalingFactor()*rect.Origin.Y, this.getScalingFactor()*image.width, this.getScalingFactor()*image.height); 24 | } 25 | } 26 | public onNewFrame(image: HTMLImageElement, monitor: Monitor, rect: Rect): void 27 | { 28 | if (this.Monitor && this.ThisCanvas && this.ThisCanvas.nativeElement && this.Monitor.Id == monitor.Id) { 29 | this.ThisCanvas.nativeElement.getContext("2d").drawImage(image, 0, 0, this.getScalingFactor()*image.width, this.getScalingFactor()*image.height); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/client/src/app/options.dialog/monitorstowatch.component.html: -------------------------------------------------------------------------------- 1 |

{{warningtext}}

2 | 3 | 4 | {{monitor.Name}} {{monitor.Width}}x{{monitor.Height}} 5 | 6 | -------------------------------------------------------------------------------- /src/client/src/app/options.dialog/monitorstowatch.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Inject, Input, OnInit} from '@angular/core'; 2 | import {AbstractControl, FormArray, FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms'; 3 | import {MAT_DIALOG_DATA, MatCheckboxChange} from '@angular/material'; 4 | 5 | import {ClientSettings, Monitor} from '../lib/rat_lite'; 6 | 7 | @Component({selector : 'monitors-towatch', templateUrl : './monitorstowatch.component.html'}) 8 | export class MonitorsToWatchComponent implements OnInit { 9 | @Input() f: FormArray; 10 | @Input() MonitorsToWatch: Monitor[]; 11 | @Input() Monitors: Monitor[]; 12 | warningtext = null; 13 | constructor(private fb: FormBuilder) {} 14 | public ngOnInit(): void 15 | { 16 | this.MonitorsToWatch.forEach((a: Monitor) => { this.f.push(new FormControl(a.Id)); }); 17 | } 18 | public isChecked(mon: Monitor): boolean 19 | { 20 | var found = this.f.controls.find((a: AbstractControl) => { return a.value == mon.Id; }); 21 | if (found) { 22 | return true; 23 | } 24 | else { 25 | return false; 26 | } 27 | } 28 | public checked(changedevent: MatCheckboxChange, mon: Monitor): void 29 | { 30 | this.warningtext = null; 31 | if (changedevent.checked) { 32 | var found = this.f.controls.findIndex((a: AbstractControl) => { return a.value == mon.Id; }); 33 | if (found == -1) { 34 | this.f.push(new FormControl(mon.Id)); 35 | this.f.setErrors(null); 36 | } 37 | } 38 | else { 39 | var found = this.f.controls.findIndex((a: AbstractControl) => { return a.value == mon.Id; }); 40 | if (found != -1) { 41 | this.f.removeAt(found); 42 | } 43 | if (this.f.controls.length ==0) { 44 | this.warningtext = "You must have at least one monitor selected!"; 45 | this.f.setErrors({onemonitorneeded : true}); 46 | } 47 | else { 48 | this.f.setErrors(null); 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/client/src/app/options.dialog/options.dialog.html: -------------------------------------------------------------------------------- 1 |

Options

2 | 3 |
4 |
5 |
6 | 7 | 8 | {{ comp }} 9 | 10 | 11 |

Larger numbers == better quality, but are slower to generate

12 |
13 |
14 |
15 |
16 | Share Clipboard 17 |
18 |
19 |
20 |
21 | Encode Images as Grayscale 22 |

Grayscale will improve network and system performance!

23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/client/src/app/options.dialog/options.dialog.ts: -------------------------------------------------------------------------------- 1 | import { ClientSettings, ClipboardSharing, ImageEncoding, Monitor } from '../lib/rat_lite'; 2 | import { Component, Inject, OnInit } from '@angular/core'; 3 | import { FormBuilder, FormGroup, Validators, FormArray } from '@angular/forms'; 4 | import { MatDialogRef } from '@angular/material'; 5 | import { MAT_DIALOG_DATA } from '@angular/material'; 6 | 7 | import { OptionsModel } from '../models/connect.model'; 8 | 9 | @Component({templateUrl : './options.dialog.html'}) 10 | export class OptionsDialog implements OnInit { 11 | public f: FormGroup; 12 | submitting = false; 13 | 14 | public CompressionOptions = [ 30, 40, 50, 60, 70, 80, 90, 100 ]; 15 | constructor(public dialogRef: MatDialogRef, private fb: FormBuilder, @Inject(MAT_DIALOG_DATA) public data: OptionsModel) {} 16 | public ngOnInit(): void 17 | { 18 | this.f = this.fb.group({ 19 | ShareClip : [ this.data.ClientSettings.ShareClip, [ Validators.required ] ], 20 | ImageCompressionSetting : [ this.data.ClientSettings.ImageCompressionSetting, [ Validators.required, Validators.min(30), Validators.max(100) ] ], 21 | EncodeImagesAsGrayScale : [ this.data.ClientSettings.EncodeImagesAsGrayScale, [ Validators.required ] ], 22 | MonitorsToWatch: new FormArray([]) 23 | }); 24 | } 25 | public onSubmit(): void 26 | { 27 | if (!this.submitting && this.f.valid) { 28 | this.submitting = true; 29 | var nc = new ClientSettings(); 30 | nc.EncodeImagesAsGrayScale = this.f.value.EncodeImagesAsGrayScale ? ImageEncoding.GRAYSCALE : ImageEncoding.COLOR; 31 | nc.ImageCompressionSetting = this.f.value.ImageCompressionSetting; 32 | nc.ShareClip = this.f.value.ShareClip ? ClipboardSharing.SHARED : ClipboardSharing.NOT_SHARED; 33 | this.f.value.MonitorsToWatch.forEach((monitorid: number) => { 34 | var found = this.data.Monitors.find((m: Monitor)=>{ return m.Id == monitorid; }); 35 | if(found){ 36 | nc.MonitorsToWatch.push(found); 37 | } 38 | }); 39 | console.log(nc.MonitorsToWatch); 40 | this.dialogRef.close(nc); 41 | } 42 | } 43 | public cancel(): void{ 44 | this.dialogRef.close(null); 45 | } 46 | } -------------------------------------------------------------------------------- /src/client/src/app/validators/numericonly.ts: -------------------------------------------------------------------------------- 1 | import {FormControl} from '@angular/forms'; 2 | export function ValidateNumericOnly(control: FormControl) 3 | { 4 | if (!+control.value) { 5 | return {ValidateNumericOnly : true}; 6 | } 7 | return null; 8 | } -------------------------------------------------------------------------------- /src/client/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/client/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/client/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smasherprog/rat_lite/34bc5f9b6e2a5fd44e8223a88566726246bb00a8/src/client/src/favicon.ico -------------------------------------------------------------------------------- /src/client/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ratclient 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/client/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /src/client/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** Evergreen browsers require these. **/ 41 | import 'core-js/es6/reflect'; 42 | import 'core-js/es7/reflect'; 43 | 44 | 45 | /** 46 | * Required to support Web Animations `@angular/platform-browser/animations`. 47 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 48 | **/ 49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 50 | 51 | 52 | 53 | /*************************************************************************************************** 54 | * Zone JS is required by Angular itself. 55 | */ 56 | import 'zone.js/dist/zone'; // Included with Angular CLI. 57 | 58 | 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | 64 | /** 65 | * Date, currency, decimal and percent pipes. 66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 67 | */ 68 | // import 'intl'; // Run `npm install --save intl`. 69 | /** 70 | * Need to import at least one locale-data with intl. 71 | */ 72 | // import 'intl/locale-data/jsonp/en'; 73 | -------------------------------------------------------------------------------- /src/client/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import "~@angular/material/prebuilt-themes/indigo-pink.css"; 3 | -------------------------------------------------------------------------------- /src/client/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare const __karma__: any; 17 | declare const require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /src/client/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "baseUrl": "./", 6 | "module": "es2015", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/client/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "target": "es5", 11 | "typeRoots": [ 12 | "node_modules/@types" 13 | ], 14 | "lib": [ 15 | "es2017", 16 | "dom" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/client/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const ProgressPlugin = require('webpack/lib/ProgressPlugin'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | const autoprefixer = require('autoprefixer'); 7 | const postcssUrl = require('postcss-url'); 8 | const ConcatPlugin = require('webpack-concat-plugin'); 9 | 10 | const { NoEmitOnErrorsPlugin, LoaderOptionsPlugin, DefinePlugin, HashedModuleIdsPlugin } = require('webpack'); 11 | const { GlobCopyWebpackPlugin, BaseHrefWebpackPlugin, InsertConcatAssetsWebpackPlugin } = require('@angular/cli/plugins/webpack'); 12 | const { CommonsChunkPlugin, UglifyJsPlugin } = require('webpack').optimize; 13 | const { AotPlugin } = require('@ngtools/webpack'); 14 | 15 | const nodeModules = path.join(process.cwd(), 'node_modules'); 16 | const entryPoints = ["inline", "polyfills", "sw-register", "styles", "vendor", "main"]; 17 | const baseHref = ""; 18 | const deployUrl = ""; 19 | 20 | const isProd = (process.env.NODE_ENV === 'production'); 21 | 22 | //add all external css to be added in our index.html--> like as if it's .angular-cli.json 23 | const styles = [ 24 | "./src/styles.scss" 25 | ]; 26 | 27 | //we add all our external scripts we want to load externally, like inserting in our index.html --> like as if it's .angular-cli.json 28 | const scripts = [ 29 | ]; 30 | 31 | //create file path for each , so we use for our excludes and includes where needed 32 | let style_paths = styles.map(style_src => path.join(process.cwd(), style_src)); 33 | 34 | function getPlugins() { 35 | var plugins = []; 36 | 37 | // Always expose NODE_ENV to webpack, you can now use `process.env.NODE_ENV` 38 | // inside your code for any environment checks; UglifyJS will automatically 39 | // drop any unreachable code. 40 | plugins.push(new DefinePlugin({ 41 | "process.env.NODE_ENV": "\"production\"" 42 | })); 43 | 44 | plugins.push(new NoEmitOnErrorsPlugin()); 45 | 46 | if(scripts.length > 0){ 47 | plugins.push(new ConcatPlugin({ 48 | "uglify": false, 49 | "sourceMap": true, 50 | "name": "scripts", 51 | "fileName": "[name].bundle.js", 52 | "filesToConcat": scripts 53 | })); 54 | plugins.push(new InsertConcatAssetsWebpackPlugin([ 55 | "scripts" 56 | ])); 57 | } 58 | 59 | plugins.push(new GlobCopyWebpackPlugin({ 60 | "patterns": [ 61 | "assets", 62 | "favicon.ico" 63 | ], 64 | "globOptions": { 65 | "cwd": process.cwd() + "/src", 66 | "dot": true, 67 | "ignore": "**/.gitkeep" 68 | } 69 | })); 70 | 71 | plugins.push(new ProgressPlugin()); 72 | 73 | plugins.push(new HtmlWebpackPlugin({ 74 | "template": "./src/index.html", 75 | "filename": "./index.html", 76 | "hash": false, 77 | "inject": true, 78 | "compile": true, 79 | "favicon": false, 80 | "minify": false, 81 | "cache": true, 82 | "showErrors": true, 83 | "chunks": "all", 84 | "excludeChunks": [], 85 | "title": "Webpack App", 86 | "xhtml": true, 87 | "chunksSortMode": function sort(left, right) { 88 | let leftIndex = entryPoints.indexOf(left.names[0]); 89 | let rightindex = entryPoints.indexOf(right.names[0]); 90 | if (leftIndex > rightindex) { 91 | return 1; 92 | } 93 | else if (leftIndex < rightindex) { 94 | return -1; 95 | } 96 | else { 97 | return 0; 98 | } 99 | } 100 | })); 101 | 102 | plugins.push(new BaseHrefWebpackPlugin({})); 103 | 104 | plugins.push(new CommonsChunkPlugin({ 105 | "name": "inline", 106 | "minChunks": null 107 | })); 108 | 109 | plugins.push(new CommonsChunkPlugin({ 110 | "name": "vendor", 111 | "minChunks": (module) => module.resource && module.resource.startsWith(nodeModules), 112 | "chunks": [ 113 | "main" 114 | ] 115 | })); 116 | 117 | plugins.push(new ExtractTextPlugin({ 118 | "filename": "[name].bundle.css", 119 | "disable": true 120 | })); 121 | 122 | plugins.push(new LoaderOptionsPlugin({ 123 | "sourceMap": false, 124 | "options": { 125 | "postcss": [ 126 | autoprefixer(), 127 | postcssUrl({ 128 | "url": (obj) => { 129 | // Only convert root relative URLs, which CSS-Loader won't process into require(). 130 | if (!obj.url.startsWith('/') || obj.url.startsWith('//')) { 131 | return obj.url; 132 | } 133 | if (deployUrl.match(/:\/\//)) { 134 | // If deployUrl contains a scheme, ignore baseHref use deployUrl as is. 135 | return `${deployUrl.replace(/\/$/, '')}${obj.url}`; 136 | } 137 | else if (baseHref.match(/:\/\//)) { 138 | // If baseHref contains a scheme, include it as is. 139 | return baseHref.replace(/\/$/, '') + 140 | `/${deployUrl}/${obj.url}`.replace(/\/\/+/g, '/'); 141 | } 142 | else { 143 | // Join together base-href, deploy-url and the original URL. 144 | // Also dedupe multiple slashes into single ones. 145 | return `/${baseHref}/${deployUrl}/${obj.url}`.replace(/\/\/+/g, '/'); 146 | } 147 | } 148 | }) 149 | ], 150 | "sassLoader": { 151 | "sourceMap": false, 152 | "includePaths": [] 153 | }, 154 | "lessLoader": { 155 | "sourceMap": false 156 | }, 157 | "context": "" 158 | } 159 | })); 160 | 161 | if (isProd) { 162 | plugins.push(new HashedModuleIdsPlugin({ 163 | "hashFunction": "md5", 164 | "hashDigest": "base64", 165 | "hashDigestLength": 4 166 | })); 167 | 168 | plugins.push(new AotPlugin({ 169 | "mainPath": "main.ts", 170 | "hostReplacementPaths": { 171 | "environments/environment.ts": "environments/environment.prod.ts" 172 | }, 173 | "exclude": [], 174 | "tsConfigPath": "src/tsconfig.app.json" 175 | })); 176 | 177 | plugins.push(new UglifyJsPlugin({ 178 | "mangle": { 179 | "screw_ie8": true 180 | }, 181 | "compress": { 182 | "screw_ie8": true, 183 | "warnings": false 184 | }, 185 | "sourceMap": false 186 | })); 187 | 188 | } else { 189 | plugins.push(new AotPlugin({ 190 | "mainPath": "main.ts", 191 | "hostReplacementPaths": { 192 | "environments/environment.ts": "environments/environment.ts" 193 | }, 194 | "exclude": [], 195 | "tsConfigPath": "src/tsconfig.app.json", 196 | "skipCodeGeneration": true 197 | })); 198 | } 199 | 200 | return plugins; 201 | } 202 | 203 | module.exports = { 204 | "devtool": "source-map", 205 | "externals": { 206 | "electron": "require('electron')", 207 | "buffer": "require('buffer')", 208 | "child_process": "require('child_process')", 209 | "crypto": "require('crypto')", 210 | "events": "require('events')", 211 | "fs": "require('fs')", 212 | "http": "require('http')", 213 | "https": "require('https')", 214 | "assert": "require('assert')", 215 | "dns": "require('dns')", 216 | "net": "require('net')", 217 | "os": "require('os')", 218 | "path": "require('path')", 219 | "querystring": "require('querystring')", 220 | "readline": "require('readline')", 221 | "repl": "require('repl')", 222 | "stream": "require('stream')", 223 | "string_decoder": "require('string_decoder')", 224 | "url": "require('url')", 225 | "util": "require('util')", 226 | "zlib": "require('zlib')" 227 | }, 228 | "resolve": { 229 | "extensions": [ 230 | ".ts", 231 | ".js", 232 | ".scss", 233 | ".json" 234 | ], 235 | "aliasFields": [], 236 | "alias": { // WORKAROUND See. angular-cli/issues/5433 237 | "environments": isProd ? path.resolve(__dirname, 'src/environments/environment.prod.ts') : path.resolve(__dirname, 'src/environments/environment.ts') 238 | }, 239 | "modules": [ 240 | "./node_modules" 241 | ] 242 | }, 243 | "resolveLoader": { 244 | "modules": [ 245 | "./node_modules" 246 | ] 247 | }, 248 | "entry": { 249 | "main": [ 250 | "./src/main.ts" 251 | ], 252 | "polyfills": [ 253 | "./src/polyfills.ts" 254 | ], 255 | "styles": styles 256 | }, 257 | "output": { 258 | "path": path.join(process.cwd(), "dist"), 259 | "filename": "[name].bundle.js", 260 | "chunkFilename": "[id].chunk.js" 261 | }, 262 | "module": { 263 | "rules": [ 264 | { 265 | "enforce": "pre", 266 | "test": /\.(js|ts)$/, 267 | "loader": "source-map-loader", 268 | "exclude": [ 269 | /\/node_modules\//, 270 | path.join(__dirname, 'node_modules', '@angular/compiler') 271 | ] 272 | }, 273 | { 274 | "test": /\.html$/, 275 | "loader": "html-loader" 276 | }, 277 | { 278 | "test": /\.(eot|svg)$/, 279 | "loader": "file-loader?name=[name].[hash:20].[ext]" 280 | }, 281 | { 282 | "test": /\.(jpg|png|gif|otf|ttf|woff|woff2|cur|ani)$/, 283 | "loader": "url-loader?name=[name].[hash:20].[ext]&limit=10000" 284 | }, 285 | { 286 | "exclude": style_paths, 287 | "test": /\.css$/, 288 | "loaders": [ 289 | "exports-loader?module.exports.toString()", 290 | "css-loader?{\"sourceMap\":false,\"importLoaders\":1}", 291 | "postcss-loader" 292 | ] 293 | }, 294 | { 295 | "exclude": style_paths, 296 | "test": /\.scss$|\.sass$/, 297 | "loaders": [ 298 | "exports-loader?module.exports.toString()", 299 | "css-loader?{\"sourceMap\":false,\"importLoaders\":1}", 300 | "postcss-loader", 301 | "sass-loader" 302 | ] 303 | }, 304 | { 305 | "exclude": style_paths, 306 | "test": /\.less$/, 307 | "loaders": [ 308 | "exports-loader?module.exports.toString()", 309 | "css-loader?{\"sourceMap\":false,\"importLoaders\":1}", 310 | "postcss-loader", 311 | "less-loader" 312 | ] 313 | }, 314 | { 315 | "exclude": style_paths, 316 | "test": /\.styl$/, 317 | "loaders": [ 318 | "exports-loader?module.exports.toString()", 319 | "css-loader?{\"sourceMap\":false,\"importLoaders\":1}", 320 | "postcss-loader", 321 | "stylus-loader?{\"sourceMap\":false,\"paths\":[]}" 322 | ] 323 | }, 324 | { 325 | "include": style_paths, 326 | "test": /\.css$/, 327 | "loaders": ExtractTextPlugin.extract({ 328 | "use": [ 329 | "css-loader?{\"sourceMap\":false,\"importLoaders\":1}", 330 | "postcss-loader" 331 | ], 332 | "fallback": "style-loader", 333 | "publicPath": "" 334 | }) 335 | }, 336 | { 337 | "include": style_paths, 338 | "test": /\.scss$|\.sass$/, 339 | "loaders": ExtractTextPlugin.extract({ 340 | "use": [ 341 | "css-loader?{\"sourceMap\":false,\"importLoaders\":1}", 342 | "postcss-loader", 343 | "sass-loader" 344 | ], 345 | "fallback": "style-loader", 346 | "publicPath": "" 347 | }) 348 | }, 349 | { 350 | "include":style_paths, 351 | "test": /\.less$/, 352 | "loaders": ExtractTextPlugin.extract({ 353 | "use": [ 354 | "css-loader?{\"sourceMap\":false,\"importLoaders\":1}", 355 | "postcss-loader", 356 | "less-loader" 357 | ], 358 | "fallback": "style-loader", 359 | "publicPath": "" 360 | }) 361 | }, 362 | { 363 | "include": style_paths, 364 | "test": /\.styl$/, 365 | "loaders": ExtractTextPlugin.extract({ 366 | "use": [ 367 | "css-loader?{\"sourceMap\":false,\"importLoaders\":1}", 368 | "postcss-loader", 369 | "stylus-loader?{\"sourceMap\":false,\"paths\":[]}" 370 | ], 371 | "fallback": "style-loader", 372 | "publicPath": "" 373 | }) 374 | }, 375 | { 376 | "test": /\.ts$/, 377 | "loader": "@ngtools/webpack" 378 | } 379 | ] 380 | }, 381 | "plugins": getPlugins(), 382 | "node": { 383 | fs: "empty", 384 | global: true, 385 | crypto: "empty", 386 | tls: "empty", 387 | net: "empty", 388 | process: true, 389 | module: false, 390 | clearImmediate: false, 391 | setImmediate: false, 392 | __dirname: false, 393 | __filename: false 394 | } 395 | }; -------------------------------------------------------------------------------- /src/server/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(rat_server) 2 | 3 | if(WIN32) 4 | set(${PROJECT_NAME}_PLATFORM_SRC 5 | windows/resource.h 6 | windows/RAT_Server.rc 7 | ../Desktop_Server.ico 8 | ) 9 | add_definitions(-D_CRT_SECURE_NO_WARNINGS -DNOMINMAX) 10 | elseif(APPLE) 11 | 12 | else() 13 | 14 | endif() 15 | 16 | add_definitions( 17 | -DTEST_CERTIFICATE_PRIVATE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../../Test/private.key" 18 | -DTEST_CERTIFICATE_PUBLIC_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../../Test/public.crt" 19 | -DTEST_CERTIFICATE_PRIVATE_PASSWORD="Test pass" 20 | ) 21 | find_package(ZLIB REQUIRED) 22 | find_package(OpenSSL REQUIRED) 23 | find_package(LIBJPEGTURBO REQUIRED) 24 | 25 | include_directories( 26 | ${COMMON_INCLUDE_DIRS} 27 | ${CMAKE_CURRENT_SOURCE_DIR} 28 | ${LIBJPEGTURBO_INCLUDE_DIRS} 29 | ) 30 | add_executable(${PROJECT_NAME} 31 | Server.h 32 | main.cpp 33 | Server.cpp 34 | ServerFunctions.h 35 | ServerFunctions.cpp 36 | ${${PROJECT_NAME}_PLATFORM_SRC} 37 | ) 38 | 39 | if(WIN32) 40 | target_link_libraries(${PROJECT_NAME} Crypt32 Dwmapi) 41 | elseif(APPLE) 42 | find_library(corefoundation_lib CoreFoundation) 43 | find_library(cocoa_lib Cocoa) 44 | find_package(Threads REQUIRED) 45 | target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT} ${corefoundation_lib} ${cocoa_lib}) 46 | else() 47 | find_package(X11 REQUIRED) 48 | find_package(Threads REQUIRED) 49 | target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT} dl ${X11_LIBRARIES} ${X11_Xfixes_LIB} ${X11_XTest_LIB} ${X11_Xinerama_LIB}) 50 | endif() 51 | 52 | if(WIN32) 53 | set(LIBEXTENSION .lib) 54 | else() 55 | if(${BUILD_SHARED_LIBS}) 56 | set(LIBEXTENSION ${CMAKE_SHARED_LIBRARY_SUFFIX}) 57 | else() 58 | set(LIBEXTENSION ${CMAKE_STATIC_LIBRARY_SUFFIX}) 59 | endif() 60 | endif() 61 | 62 | target_link_libraries(${PROJECT_NAME} 63 | rat_lite 64 | ${CMAKE_BINARY_DIR}/bin/lib/${CMAKE_SHARED_LIBRARY_PREFIX}websocket_lite${LIBEXTENSION} 65 | ${CMAKE_BINARY_DIR}/bin/lib/${CMAKE_SHARED_LIBRARY_PREFIX}input_lite${LIBEXTENSION} 66 | ${CMAKE_BINARY_DIR}/bin/lib/${CMAKE_SHARED_LIBRARY_PREFIX}clipboard_lite${LIBEXTENSION} 67 | ${CMAKE_BINARY_DIR}/bin/lib/${CMAKE_SHARED_LIBRARY_PREFIX}screen_capture_lite${LIBEXTENSION} 68 | ${OPENSSL_LIBRARIES} 69 | ${ZLIB_LIBRARIES} 70 | ${LIBJPEGTURBO_LIBRARIES} 71 | ) 72 | 73 | if(${BUILD_SHARED_LIBS}) 74 | set_target_properties(${PROJECT_NAME} PROPERTIES DEFINE_SYMBOL RAT_LITE_DLL) 75 | set(librarylocation bin) 76 | if(APPLE) 77 | set(librarylocation lib) 78 | endif() 79 | 80 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 81 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 82 | ${CMAKE_BINARY_DIR}/bin/${librarylocation}/${CMAKE_SHARED_LIBRARY_PREFIX}websocket_lite${CMAKE_SHARED_LIBRARY_SUFFIX} 83 | $) 84 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 85 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 86 | ${CMAKE_BINARY_DIR}/bin/${librarylocation}/${CMAKE_SHARED_LIBRARY_PREFIX}input_lite${CMAKE_SHARED_LIBRARY_SUFFIX} 87 | $) 88 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 89 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 90 | ${CMAKE_BINARY_DIR}/bin/${librarylocation}/${CMAKE_SHARED_LIBRARY_PREFIX}clipboard_lite${CMAKE_SHARED_LIBRARY_SUFFIX} 91 | $) 92 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 93 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 94 | ${CMAKE_BINARY_DIR}/bin/${librarylocation}/${CMAKE_SHARED_LIBRARY_PREFIX}screen_capture_lite${CMAKE_SHARED_LIBRARY_SUFFIX} 95 | $) 96 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 97 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 98 | $ 99 | $) 100 | endif() 101 | -------------------------------------------------------------------------------- /src/server/Server.cpp: -------------------------------------------------------------------------------- 1 | #include "Server.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "Clipboard_Lite.h" 11 | #include "Logging.h" 12 | #include "RAT.h" 13 | #include "ScreenCapture.h" 14 | 15 | #include "WS_Lite.h" 16 | 17 | #include "ServerFunctions.h" 18 | 19 | using namespace std::chrono_literals; 20 | 21 | namespace SL { 22 | namespace RAT_Server { 23 | class ServerImpl { 24 | public: 25 | std::shared_ptr ScreenCaptureManager_; 26 | std::shared_ptr Clipboard_; 27 | std::shared_ptr IServerDriver_; 28 | 29 | RAT_Lite::Server_Status Status_ = RAT_Lite::Server_Status::SERVER_STOPPED; 30 | std::shared_mutex ClientsLock; 31 | std::vector> Clients; 32 | std::vector AllMonitors; 33 | 34 | RAT_Lite::ClipboardSharing ShareClip = RAT_Lite::ClipboardSharing::NOT_SHARED; 35 | int ImageCompressionSettingRequested = 70; 36 | int ImageCompressionSettingActual = 70; 37 | int MouseCaptureRate = 50; 38 | int ScreenImageCaptureRateRequested = 100; 39 | int ScreenImageCaptureRateActual = 100; 40 | 41 | bool IgnoreIncomingKeyboardEvents = false; 42 | bool IgnoreIncomingMouseEvents = false; 43 | RAT_Lite::ImageEncoding EncodeImagesAsGrayScale = RAT_Lite::ImageEncoding::COLOR; 44 | size_t MaxMemoryUsed = 1024 * 1024 * 100; 45 | 46 | ServerImpl() 47 | { 48 | Status_ = RAT_Lite::Server_Status::SERVER_STOPPED; 49 | Clipboard_ = Clipboard_Lite::CreateClipboard() 50 | ->onText([&](const std::string &text) { 51 | if (ShareClip == RAT_Lite::ClipboardSharing::SHARED && IServerDriver_) { 52 | SendtoAll(IServerDriver_->PrepareClipboardChanged(text)); 53 | } 54 | }) 55 | ->run(); 56 | 57 | ScreenCaptureManager_ = 58 | Screen_Capture::CreateCaptureConfiguration([&]() { 59 | AllMonitors = Screen_Capture::GetMonitors(); 60 | if (!IServerDriver_) 61 | return AllMonitors; 62 | SendtoAll(IServerDriver_->PrepareMonitorsChanged(AllMonitors)); 63 | // add everyone to the list! 64 | 65 | std::unique_lock lock(ClientsLock); 66 | onGetMonitors(Clients, AllMonitors); 67 | return AllMonitors; 68 | }) 69 | ->onNewFrame([&](const SL::Screen_Capture::Image &img, const SL::Screen_Capture::Monitor &monitor) { 70 | if (!IServerDriver_) 71 | return; 72 | decltype(Clients) clients; 73 | clients.reserve(Clients.size()); 74 | { 75 | std::unique_lock lock(ClientsLock); 76 | for (auto &a : Clients) { 77 | auto found = 78 | std::find_if(begin(a->MonitorsNeeded), end(a->MonitorsNeeded), [&monitor](auto m) { return monitor.Id == m.Id; }); 79 | if (found != end(a->MonitorsNeeded)) { 80 | clients.push_back(a); 81 | a->MonitorsNeeded.erase(found); 82 | } 83 | } 84 | } 85 | for (auto &a : clients) { 86 | auto msg = IServerDriver_->PrepareNewFrame(img, monitor, ImageCompressionSettingActual, 87 | EncodeImagesAsGrayScale == RAT_Lite::ImageEncoding::GRAYSCALE || 88 | a->EncodeImagesAsGrayScale == RAT_Lite::ImageEncoding::GRAYSCALE); 89 | a->Socket->send(msg, SL::WS_LITE::CompressionOptions::NO_COMPRESSION); 90 | } 91 | }) 92 | ->onFrameChanged([&](const SL::Screen_Capture::Image &img, const SL::Screen_Capture::Monitor &monitor) { 93 | if (!IServerDriver_) 94 | return; 95 | 96 | decltype(Clients) clients; 97 | clients.reserve(Clients.size()); 98 | { 99 | std::shared_lock lock(ClientsLock); 100 | for (auto &a : Clients) { 101 | auto found = std::find_if(begin(a->MonitorsToWatch), end(a->MonitorsToWatch), 102 | [&monitor](auto m) { return monitor.Id == m.Id; }); 103 | if (found != end(a->MonitorsToWatch)) { 104 | clients.push_back(a); 105 | } 106 | } 107 | } 108 | 109 | auto newscreencapturerate = GetNewScreenCaptureRate(IServerDriver_->MemoryUsed(), MaxMemoryUsed, ScreenImageCaptureRateActual, 110 | ScreenImageCaptureRateRequested); 111 | if (newscreencapturerate != ScreenImageCaptureRateActual) { 112 | ScreenImageCaptureRateActual = newscreencapturerate; 113 | ScreenCaptureManager_->setFrameChangeInterval(std::chrono::milliseconds(newscreencapturerate)); 114 | } 115 | ImageCompressionSettingActual = GetNewImageCompression(IServerDriver_->MemoryUsed(), MaxMemoryUsed, 116 | ImageCompressionSettingActual, ImageCompressionSettingRequested); 117 | for (auto &a : clients) { 118 | auto msg = IServerDriver_->PrepareFrameChanged(img, monitor, ImageCompressionSettingActual, 119 | EncodeImagesAsGrayScale == RAT_Lite::ImageEncoding::GRAYSCALE || 120 | a->EncodeImagesAsGrayScale == RAT_Lite::ImageEncoding::GRAYSCALE); 121 | a->Socket->send(msg, SL::WS_LITE::CompressionOptions::NO_COMPRESSION); 122 | } 123 | 124 | }) 125 | ->onMouseChanged([&](const SL::Screen_Capture::Image *img, const SL::Screen_Capture::Point &point) { 126 | if (!IServerDriver_) 127 | return; 128 | if (img) { 129 | SendtoAll(IServerDriver_->PrepareMouseImageChanged(*img)); 130 | } 131 | SendtoAll(IServerDriver_->PrepareMousePositionChanged(point)); 132 | }) 133 | ->start_capturing(); 134 | 135 | ScreenCaptureManager_->setMouseChangeInterval(std::chrono::milliseconds(MouseCaptureRate)); 136 | ScreenCaptureManager_->setFrameChangeInterval(std::chrono::milliseconds(ScreenImageCaptureRateActual)); 137 | ScreenCaptureManager_->pause(); 138 | } 139 | 140 | virtual ~ServerImpl() 141 | { 142 | ScreenCaptureManager_.reset(); 143 | Clipboard_.reset(); // make sure to prevent race conditions 144 | Status_ = RAT_Lite::Server_Status::SERVER_STOPPED; 145 | } 146 | void SendtoAll(const WS_LITE::WSMessage &msg) 147 | { 148 | std::shared_lock lock(ClientsLock); 149 | for (const auto &a : Clients) { 150 | a->Socket->send(msg, SL::WS_LITE::CompressionOptions::NO_COMPRESSION); 151 | } 152 | } 153 | void Run(unsigned short port, std::string PasswordToPrivateKey, std::string PathTo_Private_Key, std::string PathTo_Public_Certficate) 154 | { 155 | Status_ = RAT_Lite::Server_Status::SERVER_RUNNING; 156 | 157 | auto clientctx = SL::WS_LITE::CreateContext(SL::WS_LITE::ThreadCount(1)) 158 | 159 | ->NoTLS() 160 | /* 161 | ->UseTLS( 162 | [&](SL::WS_LITE::ITLSContext *context) { 163 | context->set_options(SL::WS_LITE::options::default_workarounds | 164 | SL::WS_LITE::options::no_sslv2 | SL::WS_LITE::options::no_sslv3 | SL::WS_LITE::options::single_dh_use); 165 | std::error_code ec; 166 | 167 | context->set_password_callback( 168 | [PasswordToPrivateKey](std::size_t s, SL::WS_LITE::password_purpose p) { 169 | return PasswordToPrivateKey; }, ec); if (ec) { std::cout << "set_password_callback failed: " << ec.message(); 170 | ec.clear(); 171 | } 172 | context->use_certificate_chain_file(PathTo_Public_Certficate, ec); 173 | if (ec) { 174 | std::cout << "use_certificate_chain_file failed: " << ec.message(); 175 | ec.clear(); 176 | } 177 | context->set_default_verify_paths(ec); 178 | if (ec) { 179 | std::cout << "set_default_verify_paths failed: " << ec.message(); 180 | ec.clear(); 181 | } 182 | context->use_private_key_file(std::string(PathTo_Private_Key), 183 | SL::WS_LITE::file_format::pem, ec); if (ec) { std::cout << "use_private_key_file failed: " << ec.message(); 184 | ec.clear(); 185 | } 186 | }, 187 | SL::WS_LITE::method::tlsv11_server)*/ 188 | 189 | ->CreateListener(port); 190 | IServerDriver_ = 191 | RAT_Lite::CreateServerDriverConfiguration() 192 | ->onConnection([&](const std::shared_ptr &socket) { 193 | auto c = std::make_shared(); 194 | c->Socket = socket; 195 | auto m = Screen_Capture::GetMonitors(); 196 | c->MonitorsNeeded = m; 197 | c->MonitorsToWatch = m; 198 | socket->send(IServerDriver_->PrepareMonitorsChanged(m), SL::WS_LITE::CompressionOptions::NO_COMPRESSION); 199 | std::unique_lock lock(ClientsLock); 200 | Clients.push_back(c); 201 | ScreenCaptureManager_->resume(); 202 | }) 203 | ->onMessage([&](const std::shared_ptr &socket, const WS_LITE::WSMessage &msg) { 204 | UNUSED(socket); 205 | UNUSED(msg); 206 | }) 207 | ->onDisconnection([&](const std::shared_ptr &socket, unsigned short code, const std::string &msg) { 208 | std::unique_lock lock(ClientsLock); 209 | Clients.erase(std::remove_if(std::begin(Clients), std::end(Clients), [&](auto &s) { return s->Socket == socket; }), 210 | std::end(Clients)); 211 | if (Clients.empty()) { 212 | // make sure to stop capturing if isnt needed 213 | ScreenCaptureManager_->pause(); 214 | } 215 | UNUSED(code); 216 | UNUSED(msg); 217 | }) 218 | ->onKeyUp([&](const std::shared_ptr &socket, Input_Lite::KeyCodes key) { 219 | onKeyUp(IgnoreIncomingKeyboardEvents, socket, key); 220 | }) 221 | ->onKeyDown([&](const std::shared_ptr &socket, Input_Lite::KeyCodes key) { 222 | onKeyDown(IgnoreIncomingKeyboardEvents, socket, key); 223 | }) 224 | ->onMouseUp([&](const std::shared_ptr &socket, Input_Lite::MouseButtons button) { 225 | onMouseUp(IgnoreIncomingMouseEvents, socket, button); 226 | }) 227 | ->onMouseDown([&](const std::shared_ptr &socket, Input_Lite::MouseButtons button) { 228 | onMouseDown(IgnoreIncomingMouseEvents, socket, button); 229 | }) 230 | ->onMouseScroll([&](const std::shared_ptr &socket, int offset) { 231 | onMouseScroll(IgnoreIncomingMouseEvents, socket, offset); 232 | }) 233 | ->onMousePosition([&](const std::shared_ptr &socket, const RAT_Lite::Point &pos) { 234 | onMousePosition(IgnoreIncomingMouseEvents, socket, pos); 235 | }) 236 | ->onClipboardChanged( 237 | [&](const std::string &text) { onClipboardChanged(ShareClip == RAT_Lite::ClipboardSharing::SHARED, text, Clipboard_); }) 238 | ->onClientSettingsChanged( 239 | [&](const std::shared_ptr &socket, const RAT_Lite::ClientSettings &clientsettings) { 240 | std::unique_lock lock(ClientsLock); 241 | onClientSettingsChanged(socket.get(), Clients, AllMonitors, clientsettings); 242 | }) 243 | ->Build(clientctx); 244 | clientctx->listen(); 245 | } 246 | }; // namespace RAT 247 | 248 | Server::Server() { ServerImpl_ = new ServerImpl(); } 249 | Server::~Server() { delete ServerImpl_; } 250 | void Server::ShareClipboard(RAT_Lite::ClipboardSharing share) { ServerImpl_->ShareClip = share; } 251 | RAT_Lite::ClipboardSharing Server::ShareClipboard() const { return ServerImpl_->ShareClip; } 252 | void Server::MaxConnections(int maxconnections) 253 | { 254 | if (ServerImpl_->IServerDriver_) { 255 | ServerImpl_->IServerDriver_->MaxConnections(maxconnections); 256 | } 257 | } 258 | int Server::MaxConnections() const 259 | { 260 | if (ServerImpl_->IServerDriver_) { 261 | return ServerImpl_->IServerDriver_->MaxConnections(); 262 | } 263 | return 0; 264 | } 265 | void Server::FrameChangeInterval(int delay_in_ms) 266 | { 267 | ServerImpl_->ScreenImageCaptureRateRequested = ServerImpl_->ScreenImageCaptureRateActual = delay_in_ms; 268 | if (ServerImpl_->IServerDriver_) { 269 | ServerImpl_->ScreenCaptureManager_->setFrameChangeInterval(std::chrono::milliseconds(delay_in_ms)); 270 | } 271 | } 272 | int Server::FrameChangeInterval() const { return ServerImpl_->ScreenImageCaptureRateRequested; } 273 | void Server::MouseChangeInterval(int delay_in_ms) 274 | { 275 | ServerImpl_->MouseCaptureRate = delay_in_ms; 276 | if (ServerImpl_->IServerDriver_) { 277 | ServerImpl_->ScreenCaptureManager_->setMouseChangeInterval(std::chrono::milliseconds(delay_in_ms)); 278 | } 279 | } 280 | int Server::MouseChangeInterval() const { return ServerImpl_->MouseCaptureRate; } 281 | void Server::ImageCompressionSetting(int compression) 282 | { 283 | ServerImpl_->ImageCompressionSettingRequested = ServerImpl_->ImageCompressionSettingActual = compression; 284 | } 285 | int Server::ImageCompressionSetting() const { return ServerImpl_->ImageCompressionSettingRequested; } 286 | void Server::EncodeImagesAsGrayScale(RAT_Lite::ImageEncoding usegrayscale) { ServerImpl_->EncodeImagesAsGrayScale = usegrayscale; } 287 | RAT_Lite::ImageEncoding Server::EncodeImagesAsGrayScale() const { return ServerImpl_->EncodeImagesAsGrayScale; } 288 | 289 | void Server::Server::Run(unsigned short port, std::string PasswordToPrivateKey, std::string PathTo_Private_Key, 290 | std::string PathTo_Public_Certficate) 291 | { 292 | ServerImpl_->Run(port, PasswordToPrivateKey, PathTo_Private_Key, PathTo_Public_Certficate); 293 | while (ServerImpl_->Status_ == RAT_Lite::Server_Status::SERVER_RUNNING) { 294 | std::this_thread::sleep_for(50ms); 295 | } 296 | } 297 | 298 | } // namespace RAT_Server 299 | } // namespace SL 300 | -------------------------------------------------------------------------------- /src/server/Server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "RAT.h" 3 | #include 4 | 5 | namespace SL { 6 | namespace RAT_Server { 7 | 8 | class ServerImpl; 9 | class Server { 10 | ServerImpl *ServerImpl_; 11 | 12 | public: 13 | Server(); 14 | ~Server(); 15 | void ShareClipboard(RAT_Lite::ClipboardSharing clipsharing); 16 | RAT_Lite::ClipboardSharing ShareClipboard() const; 17 | void MaxConnections(int maxconnections); 18 | int MaxConnections() const; 19 | void FrameChangeInterval(int delay_in_ms); 20 | int FrameChangeInterval() const; 21 | void MouseChangeInterval(int delay_in_ms); 22 | int MouseChangeInterval() const; 23 | // imagecompression is [0, 100] = [WORST, BEST] 24 | void ImageCompressionSetting(int compression); 25 | int ImageCompressionSetting() const; 26 | void EncodeImagesAsGrayScale(RAT_Lite::ImageEncoding encoding); 27 | RAT_Lite::ImageEncoding EncodeImagesAsGrayScale() const; 28 | 29 | void Run(unsigned short port, std::string PasswordToPrivateKey, std::string PathTo_Private_Key, std::string PathTo_Public_Certficate); 30 | }; 31 | } // namespace RAT_Server 32 | } // namespace SL 33 | -------------------------------------------------------------------------------- /src/server/ServerFunctions.cpp: -------------------------------------------------------------------------------- 1 | #include "Logging.h" 2 | #include "ServerFunctions.h" 3 | 4 | #include "Clipboard_Lite.h" 5 | #include "Input_Lite.h" 6 | #include "Logging.h" 7 | #include "RAT.h" 8 | #include "ScreenCapture.h" 9 | #include "WS_Lite.h" 10 | 11 | namespace SL { 12 | namespace RAT_Server { 13 | 14 | int GetNewScreenCaptureRate(const size_t memoryused, size_t maxmemoryused, int imgcapturerateactual, int imgcaptureraterequested) 15 | { 16 | if (memoryused >= maxmemoryused) { 17 | // decrease the capture rate to accomidate slow connections 18 | return imgcapturerateactual + 100; // slower the capture rate .... 19 | } 20 | else if (memoryused < maxmemoryused && imgcapturerateactual != imgcaptureraterequested) { 21 | // increase the capture rate here 22 | imgcapturerateactual -= 100; // increase the capture rate .... 23 | if (imgcapturerateactual <= 0) { 24 | return imgcaptureraterequested; 25 | } 26 | return imgcapturerateactual; 27 | } 28 | return imgcapturerateactual; 29 | } 30 | int GetNewImageCompression(size_t memoryused, size_t maxmemoryused, int imagecompressionactual, int imagecompressionrequested) 31 | { 32 | if (memoryused >= maxmemoryused) { 33 | if (imagecompressionactual - 10 < 30) { // anything below 30 will be pretty bad.... 34 | return imagecompressionactual; 35 | } 36 | return imagecompressionactual - 10; 37 | } 38 | else if (memoryused < maxmemoryused && imagecompressionactual != imagecompressionrequested) { 39 | imagecompressionactual += 10; 40 | if (imagecompressionactual >= imagecompressionrequested) { 41 | return imagecompressionrequested; 42 | } 43 | return imagecompressionactual; 44 | } 45 | return imagecompressionactual; 46 | } 47 | void onClientSettingsChanged(WS_LITE::IWebSocket *socket, std::vector> &clients, 48 | const std::vector &monitors, const RAT_Lite::ClientSettings &clientsettings) 49 | { 50 | auto found = std::find_if(std::begin(clients), std::end(clients), [socket](const auto &client) { return client->Socket.get() == socket; }); 51 | if (found != std::end(clients)) { 52 | (*found)->EncodeImagesAsGrayScale = clientsettings.EncodeImagesAsGrayScale; 53 | (*found)->ImageCompressionSetting = clientsettings.ImageCompressionSetting; 54 | // cap compressionsettings 55 | if ((*found)->ImageCompressionSetting < 30) { 56 | (*found)->ImageCompressionSetting = 30; 57 | } 58 | else if ((*found)->ImageCompressionSetting > 100) { 59 | (*found)->ImageCompressionSetting = 100; 60 | } 61 | (*found)->ShareClip = clientsettings.ShareClip; 62 | for (auto &m : clientsettings.MonitorsToWatch) { 63 | auto newmonitor = std::find_if(std::begin(monitors), std::end(monitors), [&m](const auto &mon) { return mon.Id == m.Id; }); 64 | if (newmonitor == std::end(monitors)) { 65 | (*found)->MonitorsNeeded.push_back(m); 66 | } 67 | } 68 | (*found)->MonitorsToWatch = clientsettings.MonitorsToWatch; 69 | } 70 | } 71 | void onGetMonitors(std::vector> &clients, const std::vector &monitors) 72 | { 73 | for (auto &a : clients) { 74 | for (auto &monitor : monitors) { 75 | auto clientneeds = 76 | std::find_if(begin(a->MonitorsToWatch), end(a->MonitorsToWatch), [&monitor](const auto &m) { return m.Id == monitor.Id; }); 77 | a->MonitorsNeeded = monitors; 78 | } 79 | } 80 | } 81 | 82 | void onKeyUp(bool ignoreIncomingKeyboardEvents, const std::shared_ptr &socket, const Input_Lite::KeyCodes &keycode) 83 | { 84 | if (!ignoreIncomingKeyboardEvents) { 85 | Input_Lite::KeyEvent kevent; 86 | kevent.Key = keycode; 87 | kevent.Pressed = false; 88 | Input_Lite::SendInput(kevent); 89 | } 90 | UNUSED(socket); 91 | } 92 | 93 | void onKeyDown(bool ignoreIncomingKeyboardEvents, const std::shared_ptr &socket, const Input_Lite::KeyCodes &keycode) 94 | { 95 | if (!ignoreIncomingKeyboardEvents) { 96 | Input_Lite::KeyEvent kevent; 97 | kevent.Key = keycode; 98 | kevent.Pressed = true; 99 | Input_Lite::SendInput(kevent); 100 | } 101 | UNUSED(socket); 102 | } 103 | 104 | void onMouseScroll(bool ignoreIncomingMouseEvents, const std::shared_ptr &socket, int offset) 105 | { 106 | if (!ignoreIncomingMouseEvents) { 107 | Input_Lite::MouseScrollEvent mbevent; 108 | mbevent.Offset = offset; 109 | Input_Lite::SendInput(mbevent); 110 | } 111 | UNUSED(socket); 112 | } 113 | 114 | void onMouseUp(bool ignoreIncomingMouseEvents, const std::shared_ptr &socket, const Input_Lite::MouseButtons &button) 115 | { 116 | if (!ignoreIncomingMouseEvents) { 117 | Input_Lite::MouseButtonEvent mbevent; 118 | mbevent.Pressed = false; 119 | mbevent.Button = button; 120 | Input_Lite::SendInput(mbevent); 121 | } 122 | UNUSED(socket); 123 | } 124 | void onMouseDown(bool ignoreIncomingMouseEvents, const std::shared_ptr &socket, const Input_Lite::MouseButtons &button) 125 | { 126 | if (!ignoreIncomingMouseEvents) { 127 | Input_Lite::MouseButtonEvent mbevent; 128 | mbevent.Pressed = true; 129 | mbevent.Button = button; 130 | Input_Lite::SendInput(mbevent); 131 | } 132 | UNUSED(socket); 133 | } 134 | void onMousePosition(bool ignoreIncomingMouseEvents, const std::shared_ptr &socket, const RAT_Lite::Point &pos) 135 | { 136 | if (!ignoreIncomingMouseEvents) { 137 | Input_Lite::MousePositionAbsoluteEvent mbevent; 138 | mbevent.X = pos.X; 139 | mbevent.Y = pos.Y; 140 | Input_Lite::SendInput(mbevent); 141 | } 142 | UNUSED(socket); 143 | } 144 | void onClipboardChanged(bool shareclip, const std::string &str, std::shared_ptr clipboard) 145 | { 146 | if (shareclip) { 147 | clipboard->copy(str); 148 | } 149 | } 150 | } // namespace RAT_Server 151 | } // namespace SL 152 | -------------------------------------------------------------------------------- /src/server/ServerFunctions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Clipboard_Lite.h" 3 | #include "RAT.h" 4 | #include 5 | #include 6 | 7 | namespace SL { 8 | namespace RAT_Server { 9 | struct Point; 10 | 11 | struct Client { 12 | RAT_Lite::ClipboardSharing ShareClip = RAT_Lite::ClipboardSharing::NOT_SHARED; 13 | int ImageCompressionSetting = 70; 14 | 15 | bool IgnoreIncomingKeyboardEvents = false; 16 | bool IgnoreIncomingMouseEvents = false; 17 | RAT_Lite::ImageEncoding EncodeImagesAsGrayScale = RAT_Lite::ImageEncoding::COLOR; 18 | std::vector MonitorsToWatch; 19 | std::vector MonitorsNeeded; 20 | 21 | std::shared_ptr Socket; 22 | }; 23 | 24 | int GetNewScreenCaptureRate(size_t memoryused, size_t maxmemoryused, int imgcapturerateactual, int imgcaptureraterequested); 25 | int GetNewImageCompression(size_t memoryused, size_t maxmemoryused, int imagecompressionactual, int imagecompressionrequested); 26 | 27 | void onClientSettingsChanged(WS_LITE::IWebSocket *socket, std::vector> &clients, 28 | const std::vector &monitors, const RAT_Lite::ClientSettings &clientsettings); 29 | void onGetMonitors(std::vector> &clients, const std::vector &monitors); 30 | 31 | void onKeyUp(bool ignoreIncomingKeyboardEvents, const std::shared_ptr &socket, const Input_Lite::KeyCodes &keycode); 32 | 33 | void onKeyUp(bool ignoreIncomingKeyboardEvents, const std::shared_ptr &socket, const Input_Lite::KeyCodes &keycode); 34 | void onKeyDown(bool ignoreIncomingKeyboardEvents, const std::shared_ptr &socket, const Input_Lite::KeyCodes &keycode); 35 | 36 | void onMouseUp(bool ignoreIncomingMouseEvents, const std::shared_ptr &socket, const Input_Lite::MouseButtons &button); 37 | void onMouseDown(bool ignoreIncomingMouseEvents, const std::shared_ptr &socket, const Input_Lite::MouseButtons &button); 38 | void onMouseScroll(bool ignoreIncomingMouseEvents, const std::shared_ptr &socket, int offset); 39 | void onMousePosition(bool ignoreIncomingMouseEvents, const std::shared_ptr &socket, const RAT_Lite::Point &pos); 40 | void onClipboardChanged(bool shareclipboard, const std::string &str, std::shared_ptr clipboard); 41 | } // namespace RAT_Server 42 | } // namespace SL 43 | -------------------------------------------------------------------------------- /src/server/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Server.h" 2 | #include "cxxopts.hpp" 3 | #include 4 | #include 5 | 6 | #if _WIN32 7 | #include "windows/resource.h" 8 | #endif 9 | 10 | template void check_range(const std::string &name, T &value, const T &min, const T &max) 11 | { 12 | if (value < min || value > max) { 13 | std::cout << name << " must be between [" << std::to_string(min) << " and " << std::to_string(max) << "] Value found " 14 | << std::to_string(value) << std::endl; 15 | exit(1); 16 | } 17 | } 18 | 19 | int main(int argc, char *argv[]) 20 | { 21 | std::string PasswordToPrivateKey, PathTo_Private_Key, PathTo_Public_Certficate; 22 | unsigned short tlsport = 6001; 23 | bool shareclipboard = false; 24 | int ImageCompressionSetting = 70; 25 | int MouseCaptureRate = 50; 26 | int ScreenImageCaptureRate = 100; 27 | bool SendGrayScaleImages = false; 28 | int MaxNumConnections = 10; 29 | int MaxWebSocketThreads = 1; 30 | 31 | cxxopts::Options options("Remote Access Server", ""); 32 | options.add_options()("help", "")("image_compression", "Jpeg Compression setting [30, 100]", 33 | cxxopts::value(ImageCompressionSetting))( 34 | "websocket_port", "websocket listen port", cxxopts::value(tlsport)->default_value("6001"))( 35 | "share_clipboard", "share this servers clipboard with clients", cxxopts::value(shareclipboard))( 36 | "mouse_capture_rate", "mouse capture rate in ms", cxxopts::value(MouseCaptureRate)->default_value("50"))( 37 | "screen_capture_rate", "screen capture rate in ms", cxxopts::value(ScreenImageCaptureRate)->default_value("100"))( 38 | "images_as_grayscale", "send images as grayscale, this improves performance significantly", cxxopts::value(SendGrayScaleImages))( 39 | "max_connections", "maximum number of concurrent connections -1 is unlimited", cxxopts::value(MaxNumConnections)->default_value("10"))( 40 | "max_websocket_threads", "maximum number of threads to handle web socket threads numbers between 1 and 8 are valid", 41 | cxxopts::value(MaxWebSocketThreads)->default_value("2")) 42 | #if defined(DEBUG) || defined(_DEBUG) || !defined(NDEBUG) 43 | ("private_key_path", "path to the private key file", 44 | cxxopts::value(PathTo_Private_Key)->default_value(TEST_CERTIFICATE_PRIVATE_PATH))( 45 | "private_key_password", "password to the private key file", 46 | cxxopts::value(PasswordToPrivateKey)->default_value(TEST_CERTIFICATE_PRIVATE_PASSWORD))( 47 | "public_cert_path", "path to the public certificate file", 48 | cxxopts::value(PathTo_Public_Certficate)->default_value(TEST_CERTIFICATE_PUBLIC_PATH)) 49 | #else 50 | ("private_key_path", "path to the private key file", cxxopts::value(PathTo_Private_Key))( 51 | "private_key_password", "password to the private key file", cxxopts::value(PasswordToPrivateKey))( 52 | "public_cert_path", "path to the public certificate file", cxxopts::value(PathTo_Public_Certficate)) 53 | #endif 54 | ; 55 | 56 | options.parse(argc, argv); 57 | if (options.count("help")) { 58 | std::cout << options.help({"", "Group"}) << std::endl; 59 | exit(0); 60 | } 61 | 62 | SL::RAT_Server::Server serv; 63 | serv.ShareClipboard(shareclipboard ? SL::RAT_Lite::ClipboardSharing::SHARED : SL::RAT_Lite::ClipboardSharing::NOT_SHARED); 64 | serv.ImageCompressionSetting(ImageCompressionSetting); 65 | serv.FrameChangeInterval(ScreenImageCaptureRate); 66 | serv.MouseChangeInterval(MouseCaptureRate); 67 | serv.MaxConnections(MaxNumConnections); 68 | serv.EncodeImagesAsGrayScale(SendGrayScaleImages ? SL::RAT_Lite::ImageEncoding::GRAYSCALE : SL::RAT_Lite::ImageEncoding::COLOR); 69 | 70 | serv.Run(tlsport, PasswordToPrivateKey, PathTo_Private_Key, PathTo_Public_Certficate); 71 | return 0; 72 | } -------------------------------------------------------------------------------- /src/server/windows/RAT_Server.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smasherprog/rat_lite/34bc5f9b6e2a5fd44e8223a88566726246bb00a8/src/server/windows/RAT_Server.rc -------------------------------------------------------------------------------- /src/server/windows/resource.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smasherprog/rat_lite/34bc5f9b6e2a5fd44e8223a88566726246bb00a8/src/server/windows/resource.h -------------------------------------------------------------------------------- /test/AboutKeyAndCert.txt: -------------------------------------------------------------------------------- 1 | These two files I generated for testing purposes. 2 | THIS SHOULD NOT BE USED FOR PRODUCTION.. GENERATE OR GET REAL FILES! 3 | The password for the private key file is 4 | 5 | Test pass 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(rat_test) 2 | 3 | 4 | if(MSVC) 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /wd4100") 6 | add_definitions(-D_CRT_SECURE_NO_WARNINGS -DNOMINMAX) 7 | else() 8 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic") 9 | endif() 10 | 11 | 12 | add_definitions( 13 | -DTEST_CERTIFICATE_PRIVATE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../Test/private.key" 14 | -DTEST_CERTIFICATE_PUBLIC_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../Test/public.crt" 15 | -DTEST_CERTIFICATE_PRIVATE_PASSWORD="Test pass" 16 | ) 17 | 18 | include_directories(${COMMON_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR}) 19 | 20 | find_package(ZLIB REQUIRED) 21 | find_package(OpenSSL REQUIRED) 22 | find_package(LIBJPEGTURBO REQUIRED) 23 | 24 | include_directories( 25 | ${COMMON_INCLUDE_DIRS} 26 | ${CMAKE_CURRENT_SOURCE_DIR} 27 | ${LIBJPEGTURBO_INCLUDE_DIRS} 28 | ) 29 | add_executable(${PROJECT_NAME} 30 | main.cpp 31 | ) 32 | 33 | if(WIN32) 34 | target_link_libraries(${PROJECT_NAME} Crypt32 Dwmapi) 35 | elseif(APPLE) 36 | find_library(corefoundation_lib CoreFoundation) 37 | find_library(cocoa_lib Cocoa) 38 | find_package(Threads REQUIRED) 39 | target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT} ${corefoundation_lib} ${cocoa_lib}) 40 | else() 41 | find_package(X11 REQUIRED) 42 | find_package(Threads REQUIRED) 43 | target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT} dl ${X11_LIBRARIES} ${X11_Xfixes_LIB} ${X11_XTest_LIB} ${X11_Xinerama_LIB}) 44 | endif() 45 | 46 | if(WIN32) 47 | set(LIBEXTENSION .lib) 48 | else() 49 | if(${BUILD_SHARED_LIBS}) 50 | set(LIBEXTENSION ${CMAKE_SHARED_LIBRARY_SUFFIX}) 51 | else() 52 | set(LIBEXTENSION ${CMAKE_STATIC_LIBRARY_SUFFIX}) 53 | endif() 54 | endif() 55 | 56 | target_link_libraries(${PROJECT_NAME} 57 | rat_lite 58 | ${CMAKE_BINARY_DIR}/bin/lib/${CMAKE_SHARED_LIBRARY_PREFIX}websocket_lite${LIBEXTENSION} 59 | ${CMAKE_BINARY_DIR}/bin/lib/${CMAKE_SHARED_LIBRARY_PREFIX}input_lite${LIBEXTENSION} 60 | ${CMAKE_BINARY_DIR}/bin/lib/${CMAKE_SHARED_LIBRARY_PREFIX}clipboard_lite${LIBEXTENSION} 61 | ${CMAKE_BINARY_DIR}/bin/lib/${CMAKE_SHARED_LIBRARY_PREFIX}screen_capture_lite${LIBEXTENSION} 62 | ${OPENSSL_LIBRARIES} 63 | ${ZLIB_LIBRARIES} 64 | ${LIBJPEGTURBO_LIBRARIES} 65 | ) 66 | message(STATUS ${JPEG_LIBRARIES}) 67 | if(${BUILD_SHARED_LIBS}) 68 | set_target_properties(${PROJECT_NAME} PROPERTIES DEFINE_SYMBOL RAT_LITE_DLL) 69 | set(librarylocation bin) 70 | if(APPLE) 71 | set(librarylocation lib) 72 | endif() 73 | 74 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 75 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 76 | ${CMAKE_BINARY_DIR}/bin/${librarylocation}/${CMAKE_SHARED_LIBRARY_PREFIX}websocket_lite${CMAKE_SHARED_LIBRARY_SUFFIX} 77 | $) 78 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 79 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 80 | ${CMAKE_BINARY_DIR}/bin/${librarylocation}/${CMAKE_SHARED_LIBRARY_PREFIX}input_lite${CMAKE_SHARED_LIBRARY_SUFFIX} 81 | $) 82 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 83 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 84 | ${CMAKE_BINARY_DIR}/bin/${librarylocation}/${CMAKE_SHARED_LIBRARY_PREFIX}clipboard_lite${CMAKE_SHARED_LIBRARY_SUFFIX} 85 | $) 86 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 87 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 88 | ${CMAKE_BINARY_DIR}/bin/${librarylocation}/${CMAKE_SHARED_LIBRARY_PREFIX}screen_capture_lite${CMAKE_SHARED_LIBRARY_SUFFIX} 89 | $) 90 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 91 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 92 | $ 93 | $) 94 | endif() 95 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "RAT.h" 3 | #include "ScreenCapture.h" 4 | #include "WS_Lite.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std::chrono_literals; 13 | 14 | std::vector MonitorsToSend; 15 | SL::RAT_Lite::Point mpoint = {67, 89}; 16 | std::string cliptext = "thisitheweasdsxzzxc436t456u7658u/asd."; 17 | // 18 | // class TestClientDriver : public SL::RAT::IClientDriver { 19 | // public: 20 | // std::shared_ptr clientconfig; 21 | // std::unique_ptr lowerlevel; 22 | // 23 | // TestClientDriver() 24 | // { 25 | // clientconfig = std::make_shared(); 26 | // clientconfig->HttpTLSPort = 8080; 27 | // clientconfig->WebSocketTLSLPort = 6001; 28 | // clientconfig->Share_Clipboard = true; 29 | // clientconfig->PathTo_Public_Certficate = TEST_CERTIFICATE_PUBLIC_PATH; 30 | // lowerlevel = std::make_unique(this); 31 | // } 32 | // 33 | // virtual ~TestClientDriver() {} 34 | // virtual void onMonitorsChanged(const std::vector &monitors) override 35 | // { 36 | // SL_RAT_LOG(SL::RAT::Logging_Levels::INFO_log_level, "Received Monitors from Server " << monitors.size()); 37 | // assert(monitors.size() == MonitorsToSend.size()); 38 | // 39 | // for (size_t i = 0; i < monitors.size(); i++) { 40 | // assert(MonitorsToSend[i].Height == monitors[i].Height); 41 | // assert(MonitorsToSend[i].Id == monitors[i].Id); 42 | // assert(MonitorsToSend[i].Index == monitors[i].Index); 43 | // assert(MonitorsToSend[i].Name == std::string(monitors[i].Name)); 44 | // assert(MonitorsToSend[i].OffsetX == monitors[i].OffsetX); 45 | // assert(MonitorsToSend[i].OffsetY == monitors[i].OffsetY); 46 | // assert(MonitorsToSend[i].Width == monitors[i].Width); 47 | // } 48 | // std::cout << "Starting Ascii test" << std::endl; 49 | // // all asci characters 50 | // for (auto k = ' '; k <= '~'; k++) { 51 | // // lowerlevel->SendKeyDown(k); 52 | // // lowerlevel->SendKeyUp(k); 53 | // } 54 | // } 55 | // virtual void onFrameChanged(const SL::RAT::Image &img, const SL::Screen_Capture::Monitor &monitor) override {} 56 | // virtual void onNewFrame(const SL::RAT::Image &img, const SL::Screen_Capture::Monitor &monitor) override {} 57 | // virtual void onMouseImageChanged(const SL::RAT::Image &img) override {} 58 | // virtual void onMousePositionChanged(const SL::RAT::Point &mevent) override 59 | // { 60 | // assert(mevent == mpoint); 61 | // lowerlevel->SendClipboardChanged(cliptext); 62 | // } 63 | // virtual void onClipboardChanged(const std::string &text) override 64 | // { 65 | // assert(text == cliptext); 66 | // std::cout << "Sending SendMousePosition test" << std::endl; 67 | // lowerlevel->SendMousePosition(mpoint); 68 | // std::cout << "Sending SendMouseDown test" << std::endl; 69 | // lowerlevel->SendMouseDown(SL::Input_Lite::MouseButtons::MIDDLE); 70 | // std::cout << "Sending SendMouseUp test" << std::endl; 71 | // lowerlevel->SendMouseUp(SL::Input_Lite::MouseButtons::RIGHT); 72 | // std::cout << "Sending SendMouseScroll test" << std::endl; 73 | // lowerlevel->SendMouseScroll(46); 74 | // std::cout << "Sending SendClipboardChanged test" << std::endl; 75 | // lowerlevel->SendClipboardChanged(cliptext); 76 | // } 77 | // 78 | // virtual void onConnection(const std::shared_ptr &socket) override {} 79 | // virtual void onMessage(const std::shared_ptr &socket, const SL::WS_LITE::WSMessage &msg) override {} 80 | // virtual void onDisconnection(const std::shared_ptr &socket, unsigned short code, const std::string &msg) override {} 81 | //}; 82 | // 83 | // class TestServerDriver : public SL::RAT::IServerDriver { 84 | // std::shared_ptr serverconfig; 85 | // std::unique_ptr lowerlevel; 86 | // char asciikeyreceived = ' '; 87 | // 88 | // public: 89 | // std::shared_ptr Socket; 90 | // 91 | // TestServerDriver() 92 | // { 93 | // serverconfig = std::make_shared(); 94 | // 95 | // serverconfig->WebSocketTLSLPort = 6001; // listen for websockets 96 | // serverconfig->HttpTLSPort = 8080; 97 | // serverconfig->Share_Clipboard = true; 98 | // 99 | // serverconfig->ImageCompressionSetting = 70; 100 | // serverconfig->MousePositionCaptureRate = 50; 101 | // serverconfig->ScreenImageCaptureRate = 100; 102 | // serverconfig->SendGrayScaleImages = false; 103 | // serverconfig->MaxNumConnections = 2; 104 | // serverconfig->MaxWebSocketThreads = 2; 105 | // serverconfig->PathTo_Private_Key = TEST_CERTIFICATE_PRIVATE_PATH; 106 | // serverconfig->PasswordToPrivateKey = TEST_CERTIFICATE_PRIVATE_PASSWORD; 107 | // serverconfig->PathTo_Public_Certficate = TEST_CERTIFICATE_PUBLIC_PATH; 108 | // 109 | // lowerlevel = std::make_unique(this, serverconfig); 110 | // 111 | // // MonitorsToSend.push_back(SL::Screen_Capture::CreateMonitor(2, 4, 1028, 2046, -1, -3, std::string("firstmonitor"))); 112 | // } 113 | // 114 | // virtual ~TestServerDriver() {} 115 | // 116 | // virtual void onConnection(const std::shared_ptr &socket) override 117 | // { 118 | // Socket = socket; 119 | // // lowerlevel->SendMonitorsChanged(socket, MonitorsToSend); 120 | // } 121 | // virtual void onMessage(const std::shared_ptr &socket, const SL::WS_LITE::WSMessage &msg) override {} 122 | // virtual void onDisconnection(const std::shared_ptr &socket, unsigned short code, const std::string &msg) override {} 123 | // virtual void onKeyUp(const std::shared_ptr &socket, SL::Input_Lite::KeyCodes key) override 124 | // { 125 | // asciikeyreceived++; 126 | // if (key >= '~') { 127 | // // lowerlevel->SendClipboardChanged(cliptext); 128 | // } 129 | // } 130 | // virtual void onKeyDown(const std::shared_ptr &socket, SL::Input_Lite::KeyCodes key) override 131 | // { 132 | // std::cout << "Received Ascii test successfully" << std::endl; 133 | // } 134 | // 135 | // virtual void onMouseUp(const std::shared_ptr &socket, SL::Input_Lite::MouseButtons button) override 136 | // { 137 | // assert(SL::Input_Lite::MouseButtons::RIGHT == button); 138 | // std::cout << "Received onMouseUp successfully" << std::endl; 139 | // } 140 | // virtual void onMouseDown(const std::shared_ptr &socket, SL::Input_Lite::MouseButtons button) override 141 | // { 142 | // assert(SL::Input_Lite::MouseButtons::MIDDLE == button); 143 | // std::cout << "Received onMouseDown successfully" << std::endl; 144 | // } 145 | // virtual void onMouseScroll(const std::shared_ptr &socket, int offset) override 146 | // { 147 | // assert(offset == 46); 148 | // std::cout << "Received onMouseScroll successfully" << std::endl; 149 | // } 150 | // virtual void onMousePosition(const std::shared_ptr &socket, const SL::RAT::Point &pos) override 151 | // { 152 | // assert(pos == mpoint); 153 | // std::cout << "Received onMousePosition successfully" << std::endl; 154 | // } 155 | // virtual void onClipboardChanged(const std::string &text) override 156 | // { 157 | // assert(text == cliptext); 158 | // std::cout << "Received onClipboardChanged successfully" << std::endl; 159 | // } 160 | //}; 161 | 162 | int main(int argc, char *argv[]) 163 | { 164 | // TestServerDriver testserver; 165 | // TestClientDriver testclient; 166 | // auto host = "127.0.0.1"; 167 | // testclient.lowerlevel->Connect(testclient.clientconfig, host); 168 | 169 | // std::this_thread::sleep_for(10s); 170 | 171 | return 0; 172 | } -------------------------------------------------------------------------------- /test/private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIIHXzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIeQODzLIihFQCAggA 3 | MB0GCWCGSAFlAwQBKgQQD/rp2ARceEoA2maWnytKTQSCBxDrBOEOi5lqwFNAaEtJ 4 | IcOv/UdwC/Snd5FFPgAzVh0qSujDLIGvJzipDTZvVfc1Cu4f/oo+5NsRAyb4gfuw 5 | vH1Sxt1LkuHR8zEHGcMBKasr62XKcQpjs2Xb18UcDkD6E2Yx7H9+8jx2omekGzT7 6 | /WAtw/B/RGMFAQHxiQcdNULUNA1NKaELwi6P3bpTzmegF5BTQ5w/pO4QknHwD5sO 7 | Z/FEVYG2SLcThRm+80bfdBK46LHlnPnX62sil7uvnqIwbphi7PZW7iaF9uWQFx79 8 | ub6Ya/u8b3NrfSi6X9+/ETlp94E/Ji8Lc2GMHqPp+oOAbyg0dtr0FXmhijFDrgTN 9 | CqhcmnYOXMMvFb0WBx+0iWtZvRwIb6JlCJ6dYdRDwQpe5r09BYytDmdriQgloqgn 10 | 3t9P1wJ16PL4/+RxzG9FWFpd/HmYdauZ27m96qm6wMBm2yBXiAk1AeMO98zkOioz 11 | eEvJ6Z4X1TvSg30km3jeD84UT+E1Vrn1WgRNYyQggDNzApyqVPu5KlWFM56fVOdk 12 | ZE4wa+a+F9njfmxbuUJafUFSwNxYSljp3zaeXGOrbhir/lUuQC73Rnq12Y29ughV 13 | I1c8LihxEUrxmbR/cIg8oVMIO1+3P8p9j0Bn2Qt12RFcnCw1C4MXNA9Gw1xf0oL0 14 | 1sY2G0xnmSbRdwjnsaExNeVB+XvYDdt/J7X2wEZv5Af28n7TvQ0SSdwvE/qRcUkh 15 | I1L/epFbPW7lgf4FEqvnVfoTCoPTyRk5PLNSJYVpeTWMDp1JYdp2UqxNam9J+FSe 16 | ddYmihbuHSdEBLS7OnTkGz4SCLthPdeYUnjWtb2wPS//Qf7xOfz/h9dOYS4CAB3H 17 | WwIPFmssXefJU7skvy5en59h+Ydceya1TTfq4o8d5942t4igH6h7IW33sfmAuAmt 18 | BVOpOTrZDI8IVYDx38yK771zK8G6UV9MzHBg3I7hKFV2t85dfpk81hdMlpXpv7ab 19 | YedzEVOP0AtPcL/kgPQFBov+KTBWtDUDPBBuqxTZVh7dDviIcQiHVSMqWrzhudR6 20 | yqUIL+V+KF42YKAKIEDN4HxR5WIv/ufEWi3+zKhxBo1xQTE+88KEA7V3WYUSaejw 21 | hWvENS5R2Zf2ZhThZVu9NBpKpseoSuZ+WqDwyXVyxUUJuOoh+ZYIi5w4lFsRXVzr 22 | oiSaOhlwqcJlnQ1PpdIwB2YqeiYyuLYbko2e+2Z3F1XZL46XK26gRpbZTioO4/At 23 | Vw+G2RSeTE/JrzMYn0Br6d3FuReqQhIUdkALESAo+levOXHC3m2VCTeZdfk4jwYa 24 | SThyfV42av3c26VzjHYBnYfYP8kVaC+qToJFs58qsUBHd2Od1OliyMHBUXCGh/jY 25 | AdRY4OLUtW+VCoGLls6V0YTKIFMFOoChC1uTiRoOol06QKLPysV0C4+xJj+mAMD5 26 | WvyKB0EmlJNriQRW0RfkC5gWIYwcNsOOjV01tOUdlMQl6d8EL2M8lcaNDICX8LdK 27 | hSt3v08knnCythvbjPzF6r8xYWb5+SMWrhXWyJ7W/npIBf19yS4UG1aIOEUoDg4g 28 | UG3+WJW4lF+9qsLnrZct/y55JTrR7GebO7ZbBY9gpTWL50q1rrG3lB9b0wL8za7T 29 | UP3xBMZZ70JkDCWYlRFShmhFRBCQS2YtCIaNpeI8mAlAXOaOJ6cG+rBWFC/urW6P 30 | dMv8Moh/ZzT7X9Ie3IirsIw3whPrWbrd1IfnLXl4DvnZNdfsHIVbjb01HtiHLR2e 31 | fyugRAiQVtE2CktS4ggkY6qkZXFTHEV0mZqJMLg2qeLK1bYiR3EnM3GpzVDETRGl 32 | jdNsbDmJB66NvHD2sSl+pXHCYuI9XW0hsCsigP4gYqNa1FJvtNXmMgPMGAWCB4+w 33 | xR7NbrXmtR8wsLS6djMsjgt2UxIdWtbyx5dYd1Iyrc0K4qJ6k2pk64ntzi/UP463 34 | CRiKup8B+0Gb5yY2atXlibESTy+q4NufIHxQfziPL6dqfbbusn4+H1aV2OM9FLrK 35 | iqEzo+zU41W/G6IdAKbEVh79FJbiQeGvkqNhlltNFu4ENWRripFLhDoS6V/F89fr 36 | jlRlXzEvg1kSdFH5B37bIGFb7O0WGp5kgoWSfq8zRyWN7Mop4oznR8unb2QwnDI0 37 | 13rgUSK+OkIhLpf+mlnXy3ih0q4Lk47Ne5Jy2byuWNucswxn3yR19TJTLTIFRIHT 38 | vo6N4FHPzqcqke8Pftrk8qKu5fAN/9FlOMZJHWUXTrOI20pSGp3OrheLrTLVwIgV 39 | vWhNbaVUCYDjSsZFBK2k6KhiRFB8qXdShNWb6N4Em/1OTKow9xmjqopzevB7ba9V 40 | 4/ITcvs1ylwkSluDoP1fpL5jZRZmrkHjpIju/qpQh7+SkZ0qp3g2t6CPPG79uaar 41 | dHEcfRl35ANQC0j95KsdzTw/Cg== 42 | -----END ENCRYPTED PRIVATE KEY----- 43 | -------------------------------------------------------------------------------- /test/public.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEaDCCAtCgAwIBAgIBADANBgkqhkiG9w0BAQ0FADA3MRowGAYDVQQKDBFUZXN0 3 | IENvbXBhbnkgTmFtZTEZMBcGA1UEAwwQVGVzdCBDb21tb24gTmFtZTAeFw0xNjA1 4 | MzAwMjA1MzRaFw0xNzA1MzAwMjA1MzRaMDcxGjAYBgNVBAoMEVRlc3QgQ29tcGFu 5 | eSBOYW1lMRkwFwYDVQQDDBBUZXN0IENvbW1vbiBOYW1lMIIBojANBgkqhkiG9w0B 6 | AQEFAAOCAY8AMIIBigKCAYEA1E7RvjjPxjo85frpN6lBOW5bK3ZXfr1lsx609001 7 | xUXz7/aX9j+to8GkOqyGapfeidg2E6WMK4HiBCiqO9Zba3EDMEGoppQJ6ntR2zIZ 8 | SYOZfnIWyZQesC5BaL2ha5h96PVC2vYsEAglqYzrc1dvxFx0EA2EArabFcW/l5nS 9 | ztAACGPr/LCsDSMcbLzFO6SDSPCuRaOTUzbzz5mfcarFib6B35OHK/TvqCBH0/Ev 10 | FfCgJL4IgsglvhN7TXht8f/s0NQtsFPFA3g4t7byrxPJKkKgVWhtxOUcKdkTKmqj 11 | c+EsF+punJJ8egXdP4nJ74Corpx0BNHPHnkJUk8ptYrZiHRGU1UqjdA1VdT19Zdu 12 | O7KBCTfL/TaXbVxrhZIRMmoOMZfxqznZ4tNiyOOPxbNOFr1Du13XFXrOrJBkm21t 13 | 5sN42hTfmQy6hv7squwidjcEWcsKpbKkbJV7CWEr3HZSZwvn679FWwrzEwJwE5sw 14 | O2W8I3zay62KgEBAEHyNiKGbAgMBAAGjfzB9MA8GA1UdEwEB/wQFMAMBAf8wDgYD 15 | VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQCXy9tA++sUxkDnnLhypnVqKqwWjARBglg 16 | hkgBhvhCAQEEBAMCAgQwKAYJYIZIAYb4QgENBBsWGWV4YW1wbGUgY29tbWVudCBl 17 | eHRlbnNpb24wDQYJKoZIhvcNAQENBQADggGBAE5wQrvWslZ1W3wlcgSEx4dhe7xJ 18 | P8QcDR56FldEDL50VfcPPMkkm1b3t7OmhC+oo64MFRD/c/dNTtMMq4fWc4g3Laap 19 | rOX6iTgXxfK4JpwFWf+dOazrEBThf3MhV7uQJZeuMr2WSJXCS5Lw9mTd8Q/nK5AB 20 | BZDY0dCwTIGQA1a/p+PZ6ZJMt4GUJuy6rN7Qc8ihRrMx+lp3JUxgVDHJ7feZASNG 21 | 2pM/UsuDCvNagyr5A58n/gwdW164UNZHXb6WYoBhRh2Q13wBI2Ejuse8xlvL5vEV 22 | WMUils+cKTzb7wo3fcu/llVcuFhr9z0CfbgK2aiylARJB3xwgPpyb6Qe3+q1foKw 23 | fdu+Rwo59fmX4dsTkySs7ZTKC4VSChAM0ac2RxSCRvPcqkHxxezrk/t+XDL6wSsL 24 | glN6yxU1w+vsYbiayCK4Pl9SHhd9yzx1Li5XYtTmjcIieTa+tmv9IR5cltMqCoSw 25 | ZydqCFmG4VbNpR42y+zIk8vcpJ8Y3u5xQJV3Lg== 26 | -----END CERTIFICATE----- 27 | --------------------------------------------------------------------------------