├── .gitignore ├── LICENSE ├── README.md ├── RemoteControl.pro ├── image.qrc ├── image ├── winIcon.ico └── winIcon.png ├── qml.qrc ├── qml ├── ControlledPage.qml ├── ControllerPage.qml ├── GlowRectangle.qml ├── MyButton.qml ├── MySystemTray.qml ├── MyToolTip.qml └── main.qml ├── qml_qmlcache.qrc └── src ├── dxgimanager.cpp ├── dxgimanager.h ├── framelesswindow.cpp ├── framelesswindow.h ├── imageprovider.cpp ├── imageprovider.h ├── main.cpp ├── networkapi.cpp ├── networkapi.h ├── remoteevent.h ├── systemapi.cpp ├── systemapi.h ├── tcp ├── controlled.cpp ├── controlled.h ├── controller.cpp ├── controller.h ├── protocol.cpp ├── protocol.h ├── socket.cpp └── socket.h └── udp ├── connection.cpp ├── connection.h ├── controlled.cpp ├── controlled.h ├── controller.cpp ├── controller.h ├── protocol.cpp ├── protocol.h ├── socket.cpp └── socket.h /.gitignore: -------------------------------------------------------------------------------- 1 | debug/ 2 | release/ 3 | android-build/ 4 | *.user 5 | *.Debug 6 | *.Release 7 | Makefile 8 | *.stash 9 | *.rc 10 | *.exe 11 | *.qmlc 12 | *.so-deployment-settings.json 13 | *.so -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 mengps 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 | # RemoteControl 2 | 3 | 尝试做一个远程控制的简单工具 4 | 5 | 一般为局域网环境下使用(热点) 6 | 7 | ------ 8 | 9 | `说明` 使用两种协议传输 [可选TCP/UDP(默认)] 10 | 11 | ``` 12 | 默认使用UDP来传输屏幕图像,额外的TCP连接来传输EVENT,包括连接状态 13 | 14 | 要使用TCP,通过在pro中添加 DEFINES += USE_TCP 15 | ``` 16 | 17 | `说明` 使用三种截屏方式 [可选DXGI/D3D/GDI(默认,且Qt自带)] 18 | 19 | ``` 20 | 通过在pro中添加: 21 | 22 | DEFINES += USE_DXGI 或者 DEFINES += USE_GDI 或者 DEFINES += USE_D3D 23 | 24 | 因为DXGI仅在 Win8 以上提供,考虑到 Win8 的使用量,所以 Win10 以上可使用 25 | ``` 26 | ------ 27 | ### 关于Windows和Android 28 | 29 | 要知道的是,因为不能完全跨平台,某些功能难以实现(水平不够) 30 | 31 | 并且界面也需要做一些调整,很麻烦 32 | 33 | ------ 34 | 35 | ### 开发环境 36 | 37 | 使用Qt/Qml开发 38 | 39 | Windows 10,Qt 5.13.0 40 | 41 | 基本未使用高版本特性,保证`Qt Version >= 5.0`即可,建议是`Qt 5.5 ~ 5.7` 42 | 43 | ------ 44 | 45 | ### 后续改进 46 | 47 | - 使用RTP传输 48 | 49 | - 不再传输图像,而是码流 50 | 51 | ------ 52 | 53 | ### 许可证 54 | 55 | 使用 `MIT LICENSE` 56 | 57 | ------ 58 | 59 | `注意` 目前传输的不是码流而是JPG图像 60 | 61 | `注意` 该软件仅用于学习,而不适合作为实际软件使用 -------------------------------------------------------------------------------- /RemoteControl.pro: -------------------------------------------------------------------------------- 1 | QT += quick gui 2 | CONFIG += c++11 3 | 4 | DEFINES += USE_TCP 5 | #else use udp 6 | 7 | #choose one of them USE_DXGI / USE_GDI / USE_D3D 8 | DEFINES += USE_DXGI 9 | #DEFINES += USE_GDI 10 | #DEFINES += USE_D3D 11 | 12 | INCLUDEPATH += ../RemoteControl/src 13 | 14 | HEADERS += \ 15 | src/framelesswindow.h \ 16 | src/networkapi.h \ 17 | src/remoteevent.h \ 18 | src/imageprovider.h \ 19 | src/systemapi.h 20 | 21 | SOURCES += \ 22 | src/framelesswindow.cpp \ 23 | src/main.cpp \ 24 | src/networkapi.cpp \ 25 | src/imageprovider.cpp \ 26 | src/systemapi.cpp 27 | 28 | if (contains(DEFINES, USE_TCP)){ 29 | message("Use Tcp") 30 | HEADERS += \ 31 | src/tcp/controlled.h \ 32 | src/tcp/controller.h \ 33 | src/tcp/protocol.h \ 34 | src/tcp/socket.h 35 | 36 | SOURCES += \ 37 | src/tcp/controlled.cpp \ 38 | src/tcp/controller.cpp \ 39 | src/tcp/protocol.cpp \ 40 | src/tcp/socket.cpp 41 | 42 | INCLUDEPATH += src/tcp 43 | }else{ 44 | message("Use Udp") 45 | HEADERS += \ 46 | src/udp/connection.h \ 47 | src/udp/controlled.h \ 48 | src/udp/controller.h \ 49 | src/udp/protocol.h \ 50 | src/udp/socket.h 51 | 52 | SOURCES += \ 53 | src/udp/connection.cpp \ 54 | src/udp/controlled.cpp \ 55 | src/udp/controller.cpp \ 56 | src/udp/protocol.cpp \ 57 | src/udp/socket.cpp 58 | 59 | INCLUDEPATH += src/udp 60 | } 61 | 62 | win32{ 63 | message("Platform: Win32") 64 | QT += winextras 65 | LIBS += -lGdi32 66 | RC_ICONS += image/winIcon.ico 67 | contains(DEFINES, USE_D3D){ 68 | message("Duplication interface: D3D") 69 | HEADERS += src/dxgimanager.h 70 | LIBS += -ld3d9 -lD3dx9 71 | } 72 | contains(DEFINES, USE_DXGI){ 73 | message("Duplication interface: DXGI") 74 | SOURCES += src/dxgimanager.cpp 75 | LIBS += -lD3D11 -lDXGI 76 | } 77 | }else{ 78 | message ("Platform: Android") 79 | } 80 | 81 | # The following define makes your compiler emit warnings if you use 82 | # any Qt feature that has been marked deprecated (the exact warnings 83 | # depend on your compiler). Refer to the documentation for the 84 | # deprecated API to know how to port your code away from it. 85 | DEFINES += QT_DEPRECATED_WARNINGS 86 | 87 | # You can also make your code fail to compile if it uses deprecated APIs. 88 | # In order to do so, uncomment the following line. 89 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 90 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 91 | 92 | RESOURCES += \ 93 | qml.qrc \ 94 | image.qrc 95 | 96 | # Default rules for deployment. 97 | qnx: target.path = /tmp/$${TARGET}/bin 98 | else: unix:!android: target.path = /opt/$${TARGET}/bin 99 | !isEmpty(target.path): INSTALLS += target 100 | -------------------------------------------------------------------------------- /image.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | image/winIcon.png 4 | 5 | 6 | -------------------------------------------------------------------------------- /image/winIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/RemoteControl/f78d76504f8ec742a7acf2fef3e97be68b4d2f48/image/winIcon.ico -------------------------------------------------------------------------------- /image/winIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/RemoteControl/f78d76504f8ec742a7acf2fef3e97be68b4d2f48/image/winIcon.png -------------------------------------------------------------------------------- /qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | qml/main.qml 4 | qml/GlowRectangle.qml 5 | qml/MyButton.qml 6 | qml/MyToolTip.qml 7 | qml/ControlledPage.qml 8 | qml/ControllerPage.qml 9 | qml/MySystemTray.qml 10 | 11 | 12 | -------------------------------------------------------------------------------- /qml/ControlledPage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import Qt.labs.platform 1.1 3 | 4 | Item { 5 | id: root 6 | 7 | property var systemTray: undefined; 8 | 9 | Component.onCompleted: { 10 | var component = Qt.createComponent("MySystemTray.qml"); 11 | var incubator = component.incubateObject(root); 12 | if (incubator.status !== Component.Ready) { 13 | incubator.onStatusChanged = function(status) { 14 | if (status === Component.Ready) 15 | root.systemTray = incubator.object; 16 | } 17 | } 18 | } 19 | 20 | Column { 21 | anchors.centerIn: parent 22 | spacing: 32 23 | 24 | Item { 25 | width: 300 26 | height: 30 27 | 28 | Text { 29 | anchors.centerIn: parent 30 | font.pointSize: 13 31 | color: "red" 32 | text: qsTr("正在被远程控制") 33 | } 34 | } 35 | 36 | Item { 37 | width: 300 38 | height: 30 39 | 40 | MyButton { 41 | id: disconnectButton 42 | anchors.centerIn: parent 43 | heightMargin: 8 44 | text: qsTr(" <-断开连接-> ") 45 | onClicked: controlled.finish(); 46 | } 47 | } 48 | 49 | Item { 50 | width: 300 51 | height: 30 52 | 53 | MyButton { 54 | id: hidetButton 55 | anchors.centerIn: parent 56 | heightMargin: 8 57 | text: qsTr(" <-最小化托盘-> ") 58 | onClicked: { 59 | mainWindow.hide(); 60 | if (root.systemTray != undefined) 61 | root.systemTray.showMessage(qsTr("提示"), qsTr("已最小化到托盘!")); 62 | } 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /qml/ControllerPage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | Item { 4 | id: root 5 | 6 | Image { 7 | id: image 8 | anchors.fill: parent 9 | sourceSize: Qt.size(parent.width, parent.height) 10 | cache: false 11 | 12 | property int frame: 0; 13 | 14 | Text { 15 | id: fpsText 16 | anchors.left: parent.left 17 | anchors.leftMargin: 50 18 | anchors.top: parent.top 19 | anchors.topMargin: 20 20 | font.pointSize: 20 21 | color: "red" 22 | } 23 | 24 | Timer { 25 | interval: 1000 26 | running: true 27 | repeat:true 28 | onTriggered: { 29 | fpsText.text = "FPS :" + image.frame; 30 | image.frame = 0; 31 | } 32 | } 33 | } 34 | 35 | Connections { 36 | target: controller 37 | onNeedUpdate: { 38 | image.frame++; 39 | image.source = "image://screen/" + Date.now(); 40 | } 41 | } 42 | 43 | MouseArea { 44 | id: controllerArea 45 | anchors.fill: parent 46 | onPressed: { 47 | let ratio = Qt.point(mouse.x / root.width, mouse.y / root.height); 48 | controller.mousePressed(ratio); 49 | } 50 | onReleased: { 51 | let ratio = Qt.point(mouse.x / root.width, mouse.y / root.height); 52 | controller.mouseReleased(ratio); 53 | } 54 | onPositionChanged: { 55 | let ratio = Qt.point(mouse.x / root.width, mouse.y / root.height); 56 | controller.mouseMoved(ratio); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /qml/GlowRectangle.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtGraphicalEffects 1.12 3 | 4 | Item { 5 | id: root 6 | 7 | property alias color: backRect.color; 8 | property alias radius: backRect.radius; 9 | property alias gradient: backRect.gradient; 10 | property alias spread: backEffect.spread; 11 | property alias glowColor: backEffect.color; 12 | property alias glowRadius: backEffect.glowRadius; 13 | 14 | RectangularGlow { 15 | id: backEffect 16 | antialiasing: true 17 | anchors.fill: backRect 18 | glowRadius: 1 19 | spread: 0.2 20 | cornerRadius: backRect.radius + glowRadius 21 | } 22 | 23 | Rectangle { 24 | id: backRect 25 | antialiasing: true 26 | anchors.fill: parent 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /qml/MyButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | GlowRectangle { 4 | id: root 5 | width: text.width + widthMargin * 2 6 | height: text.height + heightMargin * 2 7 | radius: 6 8 | glowRadius: 6 9 | color: hovered ? hoverColor : Qt.lighter(hoverColor, 1.18); 10 | 11 | property alias text: text.text; 12 | property alias fontSize: text.font.pointSize; 13 | property bool hovered: false; 14 | property int widthMargin: 10; 15 | property int heightMargin: 6; 16 | property color textColor: text.color; 17 | property color hoverColor: "#A0A0A0"; 18 | 19 | signal clicked(); 20 | signal pressed(); 21 | signal released(); 22 | signal entered(); 23 | signal exited(); 24 | 25 | Text { 26 | id: text 27 | color: "#333" 28 | x: widthMargin 29 | y: heightMargin 30 | } 31 | 32 | MouseArea { 33 | id: mouseArea 34 | anchors.fill: parent 35 | hoverEnabled: true 36 | onPressed: { 37 | root.pressed(); 38 | text.x += 1; 39 | text.y += 1; 40 | } 41 | onReleased: { 42 | root.released(); 43 | root.clicked(); 44 | text.x -= 1; 45 | text.y -= 1; 46 | } 47 | onEntered: root.hovered = true; 48 | onExited: root.hovered = false; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /qml/MySystemTray.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import Qt.labs.platform 1.1 3 | 4 | SystemTrayIcon { 5 | visible: true 6 | iconSource: "qrc:/image/winIcon.png" 7 | tooltip: qsTr("远程控制后台运行中...") 8 | 9 | function activeWindow() { 10 | mainWindow.show(); 11 | mainWindow.raise(); 12 | mainWindow.requestActivate(); 13 | } 14 | 15 | onActivated: activeWindow(); 16 | } 17 | -------------------------------------------------------------------------------- /qml/MyToolTip.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.12 3 | 4 | ToolTip { 5 | id: root 6 | font.pointSize: 9 7 | font.family: "微软雅黑" 8 | opacity: 0 9 | background: Rectangle { 10 | radius: 4 11 | color: "#AAFFFFFF" 12 | border.color: "#888" 13 | border.width: 1 14 | } 15 | 16 | NumberAnimation { 17 | id: animation 18 | target: root 19 | running: false 20 | property: "opacity" 21 | from: 0 22 | to: 1 23 | duration: 700 24 | easing.type: Easing.InOutQuad 25 | } 26 | 27 | onVisibleChanged: if (visible) animation.restart(); 28 | } 29 | -------------------------------------------------------------------------------- /qml/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Window 2.12 3 | import QtQuick.Controls 2.12 4 | import an.window 1.0 5 | 6 | FramelessWindow { 7 | id: mainWindow 8 | 9 | visible: true 10 | width: mobile ? Screen.desktopAvailableWidth : 480 11 | height: mobile ? Screen.desktopAvailableHeight : 320 12 | minimumWidth: 480 13 | minimumHeight: 320 14 | title: qsTr("远程控制") 15 | color: "#D4E6FF" 16 | movable: !mobile 17 | resizable: !mobile 18 | Component.onCompleted: { 19 | stackView.push(mainPage); 20 | } 21 | 22 | property bool mobile: Qt.platform.os == "android"; 23 | property bool connected: false; 24 | property string localIpAddress: NetworkApi.getLocalIpAddress(); 25 | 26 | Connections { 27 | target: controlled 28 | onConnected: { 29 | mainWindow.connected = true; 30 | stackView.push("ControlledPage.qml"); 31 | } 32 | onDisconnected: { 33 | mainWindow.connected = false; 34 | stackView.pop(); 35 | } 36 | } 37 | 38 | Connections { 39 | target: controller 40 | onConnected: { 41 | mainWindow.connected = true; 42 | stackView.push("ControllerPage.qml"); 43 | } 44 | onDisconnected: { 45 | mainWindow.connected = false; 46 | stackView.pop(); 47 | } 48 | } 49 | 50 | GlowRectangle { 51 | id: windowTitle 52 | clip: true 53 | radius: 5 54 | z: 5 55 | height: mobile && connected ? 0 : 40; //如果是手机,并且已经连接就不显示 56 | width: parent.width 57 | color: "#F4D1B4" 58 | glowColor: color 59 | anchors.horizontalCenter: parent.horizontalCenter 60 | anchors.top: parent.top 61 | anchors.topMargin: mobile && connected ? 0 : -radius 62 | 63 | Text { 64 | anchors.horizontalCenter: parent.horizontalCenter 65 | anchors.bottom: parent.bottom 66 | anchors.bottomMargin: 10 67 | text: qsTr("远程控制") 68 | } 69 | 70 | MyButton { 71 | id: minButton 72 | visible: enabled 73 | enabled: !mobile 74 | fontSize: 12 75 | widthMargin: 8 76 | anchors.right: maxButton.left 77 | anchors.rightMargin: 6 78 | anchors.top: maxButton.top 79 | text: " - " 80 | onClicked: mainWindow.showMinimized(); 81 | } 82 | 83 | MyButton { 84 | id: maxButton 85 | visible: enabled 86 | enabled: !mobile 87 | fontSize: 12 88 | widthMargin: 8 89 | anchors.right: closeButton.left 90 | anchors.rightMargin: 6 91 | anchors.top: closeButton.top 92 | text: " + " 93 | onClicked: mainWindow.showMaximized(); 94 | } 95 | 96 | MyButton { 97 | id: closeButton 98 | fontSize: 12 99 | widthMargin: 8 100 | anchors.right: parent.right 101 | anchors.top: parent.top 102 | text: " x " 103 | onClicked: 104 | { 105 | if (stackView.depth == 1) 106 | mainWindow.close(); 107 | else controller.finish(); 108 | } 109 | } 110 | } 111 | 112 | StackView { 113 | id: stackView 114 | width: parent.width 115 | anchors.top: windowTitle.bottom 116 | anchors.bottom: parent.bottom 117 | } 118 | 119 | Component { 120 | id: mainPage 121 | 122 | Item { 123 | 124 | Column { 125 | anchors.centerIn: parent 126 | spacing: 32 127 | 128 | Item { 129 | width: 300 130 | height: 30 131 | 132 | Text { 133 | id: ipAddressText 134 | anchors.centerIn: parent 135 | font.pointSize: 13 136 | color: "red" 137 | text: qsTr("本机IP地址:" + localIpAddress) 138 | } 139 | } 140 | 141 | Item { 142 | width: 300 143 | height: 30 144 | 145 | Item { 146 | width: remoteIpAddressText.width + remoteIpAddressEdit.width 147 | height: 30 148 | anchors.horizontalCenter: parent.horizontalCenter 149 | 150 | Text { 151 | id: remoteIpAddressText 152 | anchors.verticalCenter: parent.verticalCenter 153 | text: qsTr("远程IP地址:") 154 | } 155 | 156 | TextField { 157 | id: remoteIpAddressEdit 158 | anchors.verticalCenter: parent.verticalCenter 159 | anchors.left: remoteIpAddressText.right 160 | anchors.rightMargin: 10 161 | width: 150 162 | height: parent.height 163 | selectByMouse: true 164 | placeholderText: qsTr("输入IPv4/IPv6地址") 165 | background: Rectangle { 166 | radius: 6 167 | border.color: "#09A3DC" 168 | } 169 | validator: RegExpValidator { 170 | //IP正则 171 | regExp: /(2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2}(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}/ 172 | } 173 | } 174 | } 175 | } 176 | 177 | Item { 178 | width: 300 179 | height: 30 180 | 181 | MyButton { 182 | id: connectButton 183 | anchors.horizontalCenter: parent.horizontalCenter 184 | heightMargin: 8 185 | text: qsTr(" <-连接-> ") 186 | onClicked: controller.requestNewConnection(remoteIpAddressEdit.text) 187 | } 188 | } 189 | } 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /qml_qmlcache.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | qml/main.qml 5 | qml/GlowRectangle.qml 6 | qml/MyButton.qml 7 | qml/MyToolTip.qml 8 | qml/ControlledPage.qml 9 | qml/ControllerPage.qml 10 | qml/MySystemTray.qml 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/dxgimanager.cpp: -------------------------------------------------------------------------------- 1 | #include "dxgimanager.h" 2 | #include 3 | 4 | Texture::~Texture() 5 | { 6 | m_texture->Release(); 7 | } 8 | 9 | DxgiTextureStaging::DxgiTextureStaging(ID3D11Device *device, ID3D11DeviceContext *context) 10 | : m_device(device), m_context(context) 11 | { 12 | 13 | } 14 | 15 | DxgiTextureStaging::~DxgiTextureStaging() 16 | { 17 | m_device->Release(); 18 | m_context->Release(); 19 | } 20 | 21 | QPixmap DxgiTextureStaging::copyToImage(IDXGIResource *res) 22 | { 23 | D3D11_TEXTURE2D_DESC desc; 24 | ID3D11Texture2D *textrueRes = nullptr; 25 | HRESULT hr = res->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast(&textrueRes)); 26 | if (FAILED(hr)) { 27 | qDebug() << "Failed to ID3D11Texture2D result =" << hex << uint(hr); 28 | return QPixmap(); 29 | } 30 | textrueRes->GetDesc(&desc); 31 | 32 | D3D11_TEXTURE2D_DESC texDesc; 33 | ZeroMemory(&texDesc, sizeof(texDesc)); 34 | texDesc.Width = desc.Width; 35 | texDesc.Height = desc.Height; 36 | texDesc.MipLevels = 1; 37 | texDesc.ArraySize = 1; 38 | texDesc.SampleDesc.Count = 1; 39 | texDesc.SampleDesc.Quality = 0; 40 | texDesc.Usage = D3D11_USAGE_STAGING; 41 | texDesc.Format = desc.Format; 42 | texDesc.BindFlags = 0; 43 | texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; 44 | texDesc.MiscFlags = 0; 45 | m_device->CreateTexture2D(&texDesc, nullptr, &m_texture); 46 | m_context->CopyResource(m_texture, textrueRes); 47 | 48 | IDXGISurface1 *surface = nullptr; 49 | hr = m_texture->QueryInterface(__uuidof(IDXGISurface1), reinterpret_cast(&surface)); 50 | if (FAILED(hr)) { 51 | qDebug() << "Failed to QueryInterface IDXGISurface1 ErrorCode =" << hex << uint(hr); 52 | return QPixmap(); 53 | } 54 | 55 | DXGI_MAPPED_RECT map; 56 | surface->Map(&map, DXGI_MAP_READ); 57 | QPixmap pixmap = QPixmap::fromImage(QImage(static_cast(map.pBits), 58 | int(desc.Width), int(desc.Height), QImage::Format_ARGB32)); 59 | surface->Unmap(); 60 | surface->Release(); 61 | m_texture->Release(); 62 | 63 | return pixmap; 64 | } 65 | 66 | DxgiManager::DxgiManager() 67 | { 68 | 69 | } 70 | 71 | DxgiManager::~DxgiManager() 72 | { 73 | m_duplication->Release(); 74 | } 75 | 76 | bool DxgiManager::init() 77 | { 78 | ID3D11Device *d3dDevice = nullptr; 79 | ID3D11DeviceContext *d3dContext = nullptr; 80 | D3D_FEATURE_LEVEL feat = D3D_FEATURE_LEVEL_11_0; 81 | HRESULT hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION, &d3dDevice, &feat, &d3dContext); 82 | if (FAILED(hr)) { 83 | m_lastError = "Failed to D3D11CreateDevice ErrorCode = " + QString::number(uint(hr), 16); 84 | return false; 85 | } 86 | 87 | IDXGIDevice *dxgiDevice = nullptr; 88 | hr = d3dDevice->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast(&dxgiDevice)); 89 | if(FAILED(hr)) { 90 | m_lastError = "Failed to QueryInterface IDXGIOutput6 ErrorCode = " + QString::number(uint(hr), 16); 91 | return false; 92 | } 93 | 94 | IDXGIAdapter *dxgiAdapter = nullptr; 95 | hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), reinterpret_cast(&dxgiAdapter)); 96 | dxgiDevice->Release(); 97 | if (FAILED(hr)) { 98 | m_lastError = "Failed to Get IDXGIAdapter ErrorCode = " + QString::number(uint(hr), 16); 99 | return false; 100 | } 101 | 102 | IDXGIOutput *dxgiOutput = nullptr; 103 | QVector outputs; 104 | for(uint i = 0; dxgiAdapter->EnumOutputs(i, &dxgiOutput) != DXGI_ERROR_NOT_FOUND; ++i) { 105 | outputs.push_back(dxgiOutput); 106 | } 107 | dxgiAdapter->Release(); 108 | if (outputs.size() > 0) dxgiOutput = outputs.at(0); 109 | else { 110 | m_lastError = "Failed to IDXGIOutput is Empty!"; 111 | return false; 112 | } 113 | 114 | IDXGIOutput6 *dxgiOutput6 = nullptr; 115 | hr = dxgiOutput->QueryInterface(__uuidof(IDXGIOutput6), reinterpret_cast(&dxgiOutput6)); 116 | dxgiOutput->Release(); 117 | if (FAILED(hr)) { 118 | m_lastError = "Failed to QueryInterface IDXGIOutput6 ErrorCode = " + QString::number(uint(hr), 16); 119 | return false; 120 | } 121 | 122 | hr = dxgiOutput6->DuplicateOutput(d3dDevice, &m_duplication); 123 | dxgiOutput6->Release(); 124 | if (FAILED(hr)) { 125 | m_lastError = "Failed to DuplicateOutput ErrorCode = " + QString::number(uint(hr), 16); 126 | return false; 127 | } 128 | 129 | DXGI_OUTDUPL_DESC desc; 130 | m_duplication->GetDesc(&desc); 131 | m_texture = new DxgiTextureStaging(d3dDevice, d3dContext); 132 | if (desc.DesktopImageInSystemMemory) { 133 | qDebug() << "Desc: CPU shared with GPU"; 134 | } else { 135 | qDebug() << "Desc: CPU not shared with GPU"; 136 | } 137 | 138 | return true; 139 | } 140 | 141 | QString DxgiManager::lastError() const 142 | { 143 | return m_lastError; 144 | } 145 | 146 | QPixmap DxgiManager::grabScreen() 147 | { 148 | IDXGIResource *desktopRes; 149 | DXGI_OUTDUPL_FRAME_INFO frameInfo; 150 | while (true) { 151 | HRESULT hr = m_duplication->AcquireNextFrame(100, &frameInfo, &desktopRes); 152 | if (FAILED(hr)) { 153 | m_lastError = "Failed to AcquireNextFrame ErrorCode = " + QString::number(uint(hr), 16); 154 | return QPixmap(); 155 | } 156 | 157 | if (frameInfo.LastPresentTime.QuadPart) break; 158 | 159 | m_duplication->ReleaseFrame(); 160 | } 161 | 162 | return m_texture->copyToImage(desktopRes); 163 | } 164 | -------------------------------------------------------------------------------- /src/dxgimanager.h: -------------------------------------------------------------------------------- 1 | #ifndef DXGIMANAGER_H 2 | #define DXGIMANAGER_H 3 | 4 | #include 5 | 6 | #ifdef Q_OS_WIN 7 | # include 8 | # if defined (_WIN32_WINNT_WIN10) //win10 dxgi1_6 9 | # include 10 | # include 11 | # endif 12 | #endif 13 | 14 | class Texture 15 | { 16 | public: 17 | virtual ~Texture(); 18 | virtual QPixmap copyToImage(IDXGIResource *res) = 0; 19 | 20 | protected: 21 | ID3D11Texture2D *m_texture = nullptr; 22 | }; 23 | 24 | class DxgiTextureStaging : public Texture 25 | { 26 | public: 27 | DxgiTextureStaging(ID3D11Device *device, ID3D11DeviceContext *context); 28 | ~DxgiTextureStaging(); 29 | 30 | QPixmap copyToImage(IDXGIResource *res); 31 | 32 | private: 33 | ID3D11Device *m_device = nullptr; 34 | ID3D11DeviceContext * m_context = nullptr; 35 | }; 36 | 37 | class DxgiManager 38 | { 39 | public: 40 | DxgiManager(); 41 | ~DxgiManager(); 42 | 43 | bool init(); 44 | QString lastError() const; 45 | QPixmap grabScreen(); 46 | 47 | private: 48 | QString m_lastError = QString(); 49 | Texture *m_texture = nullptr; 50 | IDXGIOutputDuplication *m_duplication = nullptr; 51 | }; 52 | 53 | #endif // DXGIMANAGER_H 54 | -------------------------------------------------------------------------------- /src/framelesswindow.cpp: -------------------------------------------------------------------------------- 1 | #include "framelesswindow.h" 2 | 3 | static QRect MoveArea; 4 | 5 | FramelessWindow::FramelessWindow(QWindow *parent) 6 | : QQuickWindow (parent) 7 | 8 | { 9 | setFlags(flags() | Qt::Window | Qt::FramelessWindowHint); 10 | 11 | //在这里改变默认移动区域 12 | //只有鼠标在移动区域内,才能移动窗口 13 | MoveArea = { 8, 8, width() - 16, 40 }; 14 | connect(this, &QQuickWindow::widthChanged, this, [](int arg){ 15 | MoveArea.setWidth(arg - 16); 16 | }); 17 | } 18 | 19 | bool FramelessWindow::movable() const 20 | { 21 | return m_movable; 22 | } 23 | 24 | void FramelessWindow::setMovable(bool arg) 25 | { 26 | if (m_movable != arg) { 27 | m_movable = arg; 28 | emit movableChanged(); 29 | } 30 | } 31 | 32 | bool FramelessWindow::resizable() const 33 | { 34 | return m_resizable; 35 | } 36 | 37 | void FramelessWindow::setResizable(bool arg) 38 | { 39 | if (m_resizable != arg) { 40 | m_resizable = arg; 41 | emit resizableChanged(); 42 | } 43 | } 44 | 45 | void FramelessWindow::mousePressEvent(QMouseEvent *event) 46 | { 47 | m_startPos = event->globalPos(); 48 | m_oldPos = position(); 49 | m_oldSize = size(); 50 | event->ignore(); 51 | 52 | QQuickWindow::mousePressEvent(event); 53 | } 54 | 55 | void FramelessWindow::mouseReleaseEvent(QMouseEvent *event) 56 | { 57 | m_oldPos = position(); 58 | 59 | QQuickWindow::mouseReleaseEvent(event); 60 | } 61 | 62 | void FramelessWindow::mouseDoubleClickEvent(QMouseEvent *event) 63 | { 64 | if (m_currentArea == Move) { 65 | if (windowState() == Qt::WindowMaximized) { 66 | showNormal(); 67 | m_currentArea = Client; 68 | } else if (windowState() == Qt::WindowNoState) { 69 | showMaximized(); 70 | m_currentArea = Client; 71 | } 72 | } 73 | 74 | QQuickWindow::mouseDoubleClickEvent(event); 75 | } 76 | 77 | void FramelessWindow::mouseMoveEvent(QMouseEvent *event) 78 | { 79 | if (event->buttons() & Qt::LeftButton) { 80 | if (m_movable && m_currentArea == Move) { 81 | //单独处理移动区域,这样可以更快 82 | //但是需要注意,z序更高的MouseArea仍会触发 83 | setPosition(m_oldPos - m_startPos + event->globalPos()); 84 | } else if (m_resizable && m_currentArea != Move){ 85 | setWindowGeometry(event->globalPos()); 86 | } 87 | } else { 88 | QPoint pos = event->pos(); 89 | m_currentArea = getArea(pos); 90 | if (m_resizable) setCursorIcon(); 91 | } 92 | 93 | QQuickWindow::mouseMoveEvent(event); 94 | } 95 | 96 | FramelessWindow::MouseArea FramelessWindow::getArea(const QPoint &pos) 97 | { 98 | int x = pos.x(); 99 | int y = pos.y(); 100 | int w = width(); 101 | int h = height(); 102 | MouseArea area; 103 | 104 | if (x >= 0 && x <= 8 && y >= 0 && y <= 8) { 105 | area = TopLeft; 106 | } else if (x > 8 && x < (w - 8) && y >= 0 && y <= 8) { 107 | area = Top; 108 | } else if (x >=(w - 8) && x <= w && y >= 0 && y <= 8) { 109 | area = TopRight; 110 | } else if (x >= 0 && x <= 8 && y > 8 && y < (h - 8)) { 111 | area = Left; 112 | } else if (x >=(w - 8) && x <= w && y > 8 && y < (h - 8)) { 113 | area = Right; 114 | } else if (x >= 0 && x <= 8 && y >= (h - 8) && y <= h) { 115 | area = BottomLeft; 116 | } else if (x > 8 && x < (w - 8) && y >= (h - 8) && y <= h) { 117 | area = Bottom; 118 | } else if (x >=(w - 8) && x <= w && y >= (h - 8) && y <= h) { 119 | area = BottomRight; 120 | } else if (MoveArea.contains(x, y)) { 121 | area = Move; 122 | } else { 123 | area = Client; 124 | } 125 | 126 | return area; 127 | } 128 | 129 | void FramelessWindow::setWindowGeometry(const QPoint &pos) 130 | { 131 | QPoint offset = m_startPos - pos; 132 | 133 | if (offset.x() == 0 && offset.y() == 0) 134 | return; 135 | 136 | static auto set_geometry_func = [this](const QSize &size, const QPoint &pos) { 137 | QPoint temp_pos = m_oldPos; 138 | QSize temp_size = minimumSize(); 139 | if (size.width() > minimumWidth()) { 140 | temp_pos.setX(pos.x()); 141 | temp_size.setWidth(size.width()); 142 | } else { 143 | //防止瞬间拉过头,会导致错误的计算位置,这里纠正 144 | if (pos.x() != temp_pos.x()) 145 | temp_pos.setX(m_oldPos.x() + m_oldSize.width() - minimumWidth()); 146 | } 147 | if (size.height() > minimumHeight()) { 148 | temp_pos.setY(pos.y()); 149 | temp_size.setHeight(size.height()); 150 | } else { 151 | //防止瞬间拉过头,会导致错误的计算位置,这里纠正 152 | if (pos.y() != temp_pos.y()) 153 | temp_pos.setY(m_oldPos.y() + m_oldSize.height() - minimumHeight()); 154 | } 155 | setGeometry(QRect(temp_pos, temp_size)); 156 | update(); 157 | }; 158 | 159 | switch (m_currentArea) { 160 | case TopLeft: 161 | set_geometry_func(m_oldSize + QSize(offset.x(), offset.y()), m_oldPos - offset); 162 | break; 163 | case Top: 164 | set_geometry_func(m_oldSize + QSize(0, offset.y()), m_oldPos - QPoint(0, offset.y())); 165 | break; 166 | case TopRight: 167 | set_geometry_func(m_oldSize - QSize(offset.x(), -offset.y()), m_oldPos - QPoint(0, offset.y())); 168 | break; 169 | case Left: 170 | set_geometry_func(m_oldSize + QSize(offset.x(), 0), m_oldPos - QPoint(offset.x(), 0));; 171 | break; 172 | case Right: 173 | set_geometry_func(m_oldSize - QSize(offset.x(), 0), position()); 174 | break; 175 | case BottomLeft: 176 | set_geometry_func(m_oldSize + QSize(offset.x(), -offset.y()), m_oldPos - QPoint(offset.x(), 0)); 177 | break; 178 | case Bottom: 179 | set_geometry_func(m_oldSize + QSize(0, -offset.y()), position()); 180 | break; 181 | case BottomRight: 182 | set_geometry_func(m_oldSize - QSize(offset.x(), offset.y()), position()); 183 | break; 184 | default: 185 | break; 186 | } 187 | } 188 | 189 | void FramelessWindow::setCursorIcon() 190 | { 191 | static bool unset = false; 192 | 193 | switch (m_currentArea) { 194 | case TopLeft: 195 | case BottomRight: 196 | unset = true; 197 | setCursor(Qt::SizeFDiagCursor); 198 | break; 199 | case Top: 200 | case Bottom: 201 | unset = true; 202 | setCursor(Qt::SizeVerCursor); 203 | break; 204 | case TopRight: 205 | case BottomLeft: 206 | unset = true; 207 | setCursor(Qt::SizeBDiagCursor); 208 | break; 209 | case Left: 210 | case Right: 211 | unset = true; 212 | setCursor(Qt::SizeHorCursor); 213 | break; 214 | case Move: 215 | unset = true; 216 | setCursor(Qt::ArrowCursor); 217 | break; 218 | default: 219 | if (unset) { 220 | unset = false; 221 | unsetCursor(); 222 | } 223 | break; 224 | } 225 | } 226 | 227 | -------------------------------------------------------------------------------- /src/framelesswindow.h: -------------------------------------------------------------------------------- 1 | #ifndef FRAMELESSWINDOW_H 2 | #define FRAMELESSWINDOW_H 3 | 4 | #include 5 | 6 | class FramelessWindow : public QQuickWindow 7 | { 8 | Q_OBJECT 9 | 10 | Q_PROPERTY(bool movable READ movable WRITE setMovable NOTIFY movableChanged) 11 | Q_PROPERTY(bool resizable READ resizable WRITE setResizable NOTIFY resizableChanged) 12 | 13 | enum MouseArea { 14 | TopLeft = 1, 15 | Top, 16 | TopRight, 17 | Left, 18 | Move, 19 | Right, 20 | BottomLeft, 21 | Bottom, 22 | BottomRight, 23 | Client 24 | }; 25 | 26 | public: 27 | explicit FramelessWindow(QWindow *parent = nullptr); 28 | 29 | bool movable() const; 30 | void setMovable(bool arg); 31 | 32 | bool resizable() const; 33 | void setResizable(bool arg); 34 | 35 | protected: 36 | void mousePressEvent(QMouseEvent *event) override; 37 | void mouseReleaseEvent(QMouseEvent *event) override; 38 | void mouseDoubleClickEvent(QMouseEvent *event) override; 39 | void mouseMoveEvent(QMouseEvent *event) override; 40 | 41 | signals: 42 | void movableChanged(); 43 | void resizableChanged(); 44 | 45 | private: 46 | MouseArea getArea(const QPoint &pos); 47 | void setWindowGeometry(const QPoint &pos); 48 | void setCursorIcon(); 49 | 50 | bool m_movable = true; 51 | bool m_resizable = true; 52 | MouseArea m_currentArea = Move; 53 | QPoint m_startPos; 54 | QPoint m_oldPos; 55 | QSize m_oldSize; 56 | }; 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /src/imageprovider.cpp: -------------------------------------------------------------------------------- 1 | #include "imageprovider.h" 2 | 3 | ImageProvider::ImageProvider() 4 | : QQuickImageProvider(QQuickImageProvider::Pixmap) 5 | { 6 | QPixmap pixmap(100, 100); 7 | pixmap.fill(Qt::white); 8 | m_pixmap = pixmap; 9 | } 10 | 11 | QPixmap ImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) 12 | { 13 | Q_UNUSED(id); 14 | m_pixmap.scaled(requestedSize); 15 | if (size) *size = QSize(requestedSize); 16 | 17 | return m_pixmap; 18 | } 19 | -------------------------------------------------------------------------------- /src/imageprovider.h: -------------------------------------------------------------------------------- 1 | #ifndef IMAGEPROVIDER_H 2 | #define IMAGEPROVIDER_H 3 | 4 | #include 5 | 6 | class ImageProvider : public QQuickImageProvider 7 | { 8 | public: 9 | ImageProvider(); 10 | void setPixmap(const QPixmap &pixmap) { if (!pixmap.isNull()) m_pixmap = pixmap; } 11 | QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; 12 | 13 | private: 14 | QPixmap m_pixmap; 15 | }; 16 | 17 | #endif // IMAGEPROVIDER_H 18 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "networkapi.h" 2 | #include "controlled.h" 3 | #include "controller.h" 4 | #include "framelesswindow.h" 5 | #include "imageprovider.h" 6 | #include "protocol.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | int main(int argc, char *argv[]) 13 | { 14 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 15 | 16 | QGuiApplication app(argc, argv); 17 | 18 | #ifdef USE_TCP 19 | qRegisterMetaType("DataBlock"); 20 | #endif 21 | qRegisterMetaType("SocketState"); 22 | qRegisterMetaType("QHostAddress"); 23 | qRegisterMetaType("RemoteEvent"); 24 | 25 | qmlRegisterType("an.window", 1, 0, "FramelessWindow"); 26 | 27 | NetworkApi *api = new NetworkApi; 28 | Controlled *controlled = new Controlled; 29 | Controller *controller = new Controller; 30 | 31 | QQmlApplicationEngine engine; 32 | engine.rootContext()->setContextProperty("NetworkApi", api); 33 | engine.rootContext()->setContextProperty("controlled", controlled); 34 | engine.rootContext()->setContextProperty("controller", controller); 35 | engine.addImageProvider(QLatin1String("screen"), controller->getImageProvider()); 36 | engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); 37 | if (engine.rootObjects().isEmpty()) 38 | return -1; 39 | 40 | return app.exec(); 41 | } 42 | -------------------------------------------------------------------------------- /src/networkapi.cpp: -------------------------------------------------------------------------------- 1 | #include "networkapi.h" 2 | #include 3 | 4 | NetworkApi::NetworkApi(QObject *parent) 5 | : QObject (parent) 6 | { 7 | 8 | } 9 | 10 | bool NetworkApi::isLocalAddress(const QHostAddress &address) 11 | { 12 | #ifndef Q_OS_ANDROID //安卓需要一些东西 13 | auto interfaces = QNetworkInterface::allInterfaces(); 14 | QList entry; 15 | for (auto interface : interfaces) { 16 | if (interface.flags() & (QNetworkInterface::IsUp | QNetworkInterface::IsRunning)) { 17 | entry = interface.addressEntries(); 18 | QHostAddress ip = entry.at(1).ip(); 19 | if (ip.toIPv4Address() == address.toIPv4Address()) //应对IPv6->IPv4 20 | return true; 21 | entry.clear(); 22 | } 23 | } 24 | #endif 25 | return false; 26 | } 27 | 28 | QString NetworkApi::getLocalIpAddress() 29 | { 30 | QString localIp = "0.0.0.0"; 31 | #ifndef Q_OS_ANDROID 32 | auto interfaces = QNetworkInterface::allInterfaces(); 33 | QList entries; 34 | for (auto interface : interfaces) { 35 | if (interface.flags() & QNetworkInterface::IsLoopBack) //过滤环回地址 36 | continue; 37 | 38 | if (interface.flags() & (QNetworkInterface::IsUp | QNetworkInterface::IsRunning)) { 39 | entries = interface.addressEntries(); 40 | for (auto entry : entries) { 41 | if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) { 42 | if (interface.name().indexOf("wireless") != -1) { 43 | localIp = entry.ip().toString(); 44 | qDebug() << interface.humanReadableName() << interface.name() << " 无线网IP: " << localIp; 45 | } else if (interface.name().indexOf("ethernet") != -1) { 46 | //以太网ip暂时不用 47 | qDebug() << interface.humanReadableName() << interface.name() << " 以太网IP: " << entry.ip().toString(); 48 | } 49 | } 50 | } 51 | entries.clear(); 52 | } 53 | } 54 | #endif 55 | return localIp; 56 | } 57 | -------------------------------------------------------------------------------- /src/networkapi.h: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKAPI_H 2 | #define NETWORKAPI_H 3 | 4 | #include 5 | 6 | class QHostAddress; 7 | class NetworkApi : public QObject 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | NetworkApi(QObject *parent = nullptr); 13 | 14 | static bool isLocalAddress(const QHostAddress &address); 15 | Q_INVOKABLE QString getLocalIpAddress(); 16 | }; 17 | 18 | #endif // NETWORKAPI_H 19 | -------------------------------------------------------------------------------- /src/remoteevent.h: -------------------------------------------------------------------------------- 1 | #ifndef REMOTEEVENT_H 2 | #define REMOTEEVENT_H 3 | 4 | #include 5 | #include 6 | 7 | class RemoteEvent 8 | { 9 | public: 10 | enum class EventType 11 | { 12 | NoType = 0, 13 | Pressed, 14 | Released, 15 | Moved, 16 | KeyInput 17 | }; 18 | 19 | RemoteEvent() { } 20 | RemoteEvent(EventType type, const QPointF &position) 21 | : m_type(type), m_position(position) { } 22 | 23 | EventType type() const { return m_type; } 24 | void setType(EventType type) { m_type = type; } 25 | QPointF position() const { return m_position; } 26 | void setPosition(const QPointF &position) { m_position = position; } 27 | 28 | bool isEmpty() { return m_type == EventType::NoType && m_position.isNull(); } 29 | int size() { return sizeof(m_type) + sizeof(m_position) + sizeof(int); } 30 | 31 | private: 32 | EventType m_type = EventType::NoType; 33 | QPointF m_position; 34 | }; 35 | 36 | #endif // REMOTEEVENT_H 37 | -------------------------------------------------------------------------------- /src/systemapi.cpp: -------------------------------------------------------------------------------- 1 | #include "systemapi.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifdef Q_OS_WIN 11 | # include 12 | # include 13 | # if defined (USE_D3D) 14 | # include 15 | # include 16 | # include 17 | # elif defined (USE_DXGI) && defined (_WIN32_WINNT_WIN10) 18 | # include "dxgimanager.h" 19 | # endif 20 | #endif 21 | 22 | void SystemApi::mousePress(const QPointF &pos) 23 | { 24 | #ifdef Q_OS_WIN 25 | SetCursorPos(int(pos.x()), int(pos.y())); 26 | mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); 27 | #endif 28 | } 29 | 30 | void SystemApi::mouseRelease(const QPointF &pos) 31 | { 32 | #ifdef Q_OS_WIN 33 | SetCursorPos(int(pos.x()), int(pos.y())); 34 | mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); 35 | #endif 36 | } 37 | 38 | void SystemApi::mouseMove(const QPointF &pos) 39 | { 40 | #ifdef Q_OS_WIN 41 | SetCursorPos(int(pos.x()), int(pos.y())); 42 | mouse_event(MOUSEEVENTF_MOVE, 0, 0, 0, 0); 43 | #endif 44 | } 45 | 46 | QPixmap SystemApi::grabScreen() 47 | { 48 | QPixmap screen; 49 | #if defined (USE_D3D) && defined (Q_OS_WIN) 50 | static bool d3dCreated = false; 51 | static LPDIRECT3D9 lpD3D = nullptr; 52 | static LPDIRECT3DDEVICE9 lpDevice = nullptr; 53 | static D3DDISPLAYMODE ddm; 54 | 55 | if (!d3dCreated) { 56 | lpD3D = Direct3DCreate9(D3D_SDK_VERSION); 57 | lpD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &ddm); 58 | D3DPRESENT_PARAMETERS d3dpp; 59 | ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS)); 60 | d3dpp.Windowed = true; 61 | d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; 62 | d3dpp.BackBufferFormat = ddm.Format; 63 | d3dpp.BackBufferHeight = ddm.Height; 64 | d3dpp.BackBufferWidth = ddm.Width; 65 | d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; 66 | d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; 67 | d3dpp.hDeviceWindow = GetDesktopWindow(); 68 | d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; 69 | d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; 70 | 71 | lpD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, GetDesktopWindow(), 72 | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &lpDevice); 73 | d3dCreated = true; 74 | qDebug() << ddm.Width << ddm.Height << lpDevice; 75 | } 76 | 77 | if (lpDevice) { 78 | LPDIRECT3DSURFACE9 surface; 79 | lpDevice->CreateOffscreenPlainSurface(ddm.Width, ddm.Height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &surface, nullptr); 80 | lpDevice->GetFrontBufferData(0, surface); 81 | LPD3DXBUFFER bufferedImage = nullptr; 82 | D3DXSaveSurfaceToFileInMemory(&bufferedImage, D3DXIFF_JPG, surface, nullptr, nullptr); 83 | screen.loadFromData(static_cast(bufferedImage->GetBufferPointer()), bufferedImage->GetBufferSize(), "JPG"); 84 | bufferedImage->Release(); 85 | surface->Release(); 86 | } 87 | #elif defined (USE_GDI) && defined (Q_OS_WIN) 88 | int width = GetSystemMetrics(SM_CXSCREEN); 89 | int height = GetSystemMetrics(SM_CYSCREEN); 90 | 91 | HWND hwnd = GetDesktopWindow(); 92 | HDC display_dc = GetDC(nullptr); 93 | HDC bitmap_dc = CreateCompatibleDC(display_dc); 94 | HBITMAP bitmap = CreateCompatibleBitmap(display_dc, width, height); 95 | HGDIOBJ null_bitmap = SelectObject(bitmap_dc, bitmap); 96 | 97 | // copy data 98 | HDC window_dc = GetDC(hwnd); 99 | BitBlt(bitmap_dc, 0, 0, width, height, window_dc, 0, 0, SRCCOPY | CAPTUREBLT); 100 | 101 | // clean up all but bitmap 102 | ReleaseDC(hwnd, window_dc); 103 | SelectObject(bitmap_dc, null_bitmap); 104 | DeleteDC(bitmap_dc); 105 | 106 | screen = QtWin::fromHBITMAP(bitmap); 107 | DeleteObject(bitmap); 108 | ReleaseDC(nullptr, display_dc); 109 | #elif defined (USE_DXGI) && defined (Q_OS_WIN) && defined (_WIN32_WINNT_WIN10) 110 | static DxgiManager dxgiManager; 111 | static bool dxgiInit = false; 112 | if (!dxgiInit) { 113 | if (!dxgiManager.init()) { 114 | qDebug() << "DXGI Init Error :" << dxgiManager.lastError(); 115 | } else { 116 | dxgiInit = true; 117 | screen = dxgiManager.grabScreen(); 118 | } 119 | } else { 120 | screen = dxgiManager.grabScreen(); 121 | } 122 | #else 123 | screen = QGuiApplication::primaryScreen()->grabWindow(0); 124 | #endif 125 | if (!screen.isNull()) { 126 | QPainter painter(&screen); 127 | painter.drawPixmap(QCursor::pos(), grabCursor()); 128 | } 129 | 130 | return screen; 131 | } 132 | 133 | QPixmap SystemApi::grabCursor() 134 | { 135 | QPixmap cursorPixmap; 136 | #ifdef Q_OS_WIN 137 | // Get Cursor Size 138 | int cursorWidth = GetSystemMetrics(SM_CXCURSOR); 139 | int cursorHeight = GetSystemMetrics(SM_CYCURSOR); 140 | 141 | // Get your device contexts. 142 | HDC hdcScreen = GetDC(nullptr); 143 | HDC hdcMem = CreateCompatibleDC(hdcScreen); 144 | 145 | // Create the bitmap to use as a canvas. 146 | HBITMAP hbmCanvas = CreateCompatibleBitmap(hdcScreen, cursorWidth, cursorHeight); 147 | 148 | // Select the bitmap into the device context. 149 | HGDIOBJ hbmOld = SelectObject(hdcMem, hbmCanvas); 150 | 151 | // Get information about the global cursor. 152 | CURSORINFO ci; 153 | ci.cbSize = sizeof(ci); 154 | GetCursorInfo(&ci); 155 | 156 | // Draw the cursor into the canvas. 157 | DrawIcon(hdcMem, 0, 0, ci.hCursor); 158 | 159 | // Convert to QPixmap 160 | cursorPixmap = QtWin::fromHBITMAP(hbmCanvas, QtWin::HBitmapAlpha); 161 | 162 | // Clean up after yourself. 163 | SelectObject(hdcMem, hbmOld); 164 | DeleteObject(hbmCanvas); 165 | DeleteDC(hdcMem); 166 | ReleaseDC(nullptr, hdcScreen); 167 | #endif 168 | return cursorPixmap; 169 | } 170 | -------------------------------------------------------------------------------- /src/systemapi.h: -------------------------------------------------------------------------------- 1 | #ifndef SYSTEMAPI_H 2 | #define SYSTEMAPI_H 3 | 4 | #include 5 | 6 | class SystemApi 7 | { 8 | public: 9 | /** System Event */ 10 | static void mousePress(const QPointF &pos); 11 | static void mouseRelease(const QPointF &pos); 12 | static void mouseMove(const QPointF &pos); 13 | 14 | /** System Tools */ 15 | static QPixmap grabScreen(); 16 | static QPixmap grabCursor(); 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/tcp/controlled.cpp: -------------------------------------------------------------------------------- 1 | #include "controlled.h" 2 | #include "remoteevent.h" 3 | #include "systemapi.h" 4 | #include "socket.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | Controlled::Controlled(QObject *parent) 13 | : QTcpServer (parent) 14 | { 15 | listen(QHostAddress::Any, 43800); 16 | } 17 | 18 | Controlled::~Controlled() 19 | { 20 | 21 | } 22 | 23 | void Controlled::finish() 24 | { 25 | if (m_controlled) 26 | QMetaObject::invokeMethod(m_controlled, "abort"); 27 | } 28 | 29 | void Controlled::processEvent(const RemoteEvent &ev) 30 | { 31 | QRectF screenRect = qApp->primaryScreen()->geometry(); 32 | QPointF localPos(ev.position().x() * screenRect.width(), 33 | ev.position().y() * screenRect.height()); 34 | 35 | switch (ev.type()) 36 | { 37 | case RemoteEvent::EventType::Pressed: 38 | SystemApi::mousePress(localPos); 39 | break; 40 | case RemoteEvent::EventType::Released: 41 | SystemApi::mouseRelease(localPos); 42 | break; 43 | case RemoteEvent::EventType::Moved: 44 | SystemApi::mouseMove(localPos); 45 | break; 46 | case RemoteEvent::EventType::KeyInput: 47 | break; 48 | default: 49 | break; 50 | } 51 | } 52 | 53 | void Controlled::timerEvent(QTimerEvent *event) 54 | { 55 | Q_UNUSED(event); 56 | if (m_controlled) { 57 | QBuffer buffer; 58 | buffer.open(QIODevice::WriteOnly); 59 | QTime time = QTime::currentTime(); 60 | QPixmap pixmap = SystemApi::grabScreen(); 61 | qDebug() << time.msecsTo(QTime::currentTime()); 62 | pixmap.save(&buffer, "jpg", -1); 63 | BlockHeader header = { SCREEN_TYPE, qint32(buffer.size()) }; 64 | DataBlock data = { header, buffer.data() }; 65 | QMetaObject::invokeMethod(m_controlled, "writeToSocket", Q_ARG(DataBlock, data)); 66 | } 67 | } 68 | 69 | void Controlled::incomingConnection(qintptr socketDescriptor) 70 | { 71 | if (!m_controlled) { 72 | QThread *thread = new QThread; 73 | connect(thread, &QThread::finished, thread, &QThread::deleteLater); 74 | m_controlled = new Socket; 75 | connect(m_controlled, &Socket::stateChanged, this, [this](QAbstractSocket::SocketState socketState) { 76 | switch (socketState) 77 | { 78 | case QAbstractSocket::ConnectedState: 79 | emit connected(); 80 | break; 81 | default: 82 | break; 83 | } 84 | }); 85 | connect(m_controlled, &Socket::disconnected, this, [this]() { 86 | Socket *socket = m_controlled; 87 | m_controlled = nullptr; 88 | socket->deleteLater(); 89 | killTimer(m_timerId); 90 | m_timerId = 0; 91 | emit disconnected(); 92 | }); 93 | connect(m_controlled, &Socket::hasEventData, this, [this](const RemoteEvent &event) { 94 | processEvent(event); 95 | }); 96 | m_controlled->setSocketDescriptor(socketDescriptor); 97 | m_controlled->moveToThread(thread); 98 | thread->start(); 99 | 100 | if (!m_timerId) 101 | m_timerId = startTimer(std::chrono::milliseconds(30)); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/tcp/controlled.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTROLLED_H 2 | #define CONTROLLED_H 3 | 4 | #include "protocol.h" 5 | #include 6 | #include 7 | 8 | class Socket; 9 | class RemoteEvent; 10 | class Controlled : public QTcpServer 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit Controlled(QObject *parent = nullptr); 16 | ~Controlled(); 17 | 18 | Q_INVOKABLE void finish(); 19 | 20 | signals: 21 | void connected(); 22 | void disconnected(); 23 | 24 | public slots: 25 | void processEvent(const RemoteEvent &ev); 26 | 27 | protected: 28 | void timerEvent(QTimerEvent *event); 29 | void incomingConnection(qintptr socketDescriptor); 30 | 31 | private: 32 | Socket *m_controlled = nullptr; 33 | int m_timerId = 0; 34 | }; 35 | 36 | #endif // CONTROLLED_H 37 | -------------------------------------------------------------------------------- /src/tcp/controller.cpp: -------------------------------------------------------------------------------- 1 | #include "controller.h" 2 | #include "imageprovider.h" 3 | #include "networkapi.h" 4 | #include "socket.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | Controller::Controller(QObject *parent) 11 | : QObject(parent) 12 | { 13 | m_provider = new ImageProvider; 14 | 15 | m_socket = new Socket; 16 | QThread *thread = new QThread; 17 | connect(thread, &QThread::finished, thread, &QThread::deleteLater); 18 | connect(m_socket, &Socket::connected, this, &Controller::connected); 19 | connect(m_socket, &Socket::disconnected, this, &Controller::disconnected); 20 | connect(m_socket, &Socket::hasScreenData, this, [this](const QByteArray &screenData) { 21 | QPixmap pixmap; 22 | pixmap.loadFromData(screenData); 23 | m_provider->setPixmap(pixmap); 24 | emit needUpdate(); 25 | }); 26 | m_socket->moveToThread(thread); 27 | thread->start(); 28 | } 29 | 30 | void Controller::finish() 31 | { 32 | QMetaObject::invokeMethod(m_socket, "abort"); 33 | } 34 | 35 | void Controller::mousePressed(const QPointF &position) 36 | { 37 | sendRemoteEvent(RemoteEvent::EventType::Pressed, position); 38 | } 39 | 40 | void Controller::mouseReleased(const QPointF &position) 41 | { 42 | sendRemoteEvent(RemoteEvent::EventType::Released, position); 43 | } 44 | 45 | void Controller::mouseMoved(const QPointF &position) 46 | { 47 | sendRemoteEvent(RemoteEvent::EventType::Moved, position); 48 | } 49 | 50 | void Controller::requestNewConnection(const QString &address) 51 | { 52 | QHostAddress hostAddress(address); 53 | //有效且不为本机地址 54 | if (!hostAddress.isNull() && !NetworkApi::isLocalAddress(hostAddress)) { 55 | QMetaObject::invokeMethod(m_socket, "abort"); 56 | QMetaObject::invokeMethod(m_socket, "connectHost", Q_ARG(QHostAddress, hostAddress), Q_ARG(quint16, 43800)); 57 | } 58 | } 59 | 60 | void Controller::sendRemoteEvent(RemoteEvent::EventType type, const QPointF &position) 61 | { 62 | RemoteEvent event(type, position); 63 | QMetaObject::invokeMethod(m_socket, "writeToSocket", Q_ARG(RemoteEvent, event)); 64 | } 65 | -------------------------------------------------------------------------------- /src/tcp/controller.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTROLLER_H 2 | #define CONTROLLER_H 3 | 4 | #include "remoteevent.h" 5 | #include 6 | 7 | class Socket; 8 | class ImageProvider; 9 | class Controller : public QObject 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit Controller(QObject *parent = nullptr); 14 | 15 | ImageProvider* getImageProvider() { return m_provider; } 16 | 17 | Q_INVOKABLE void finish(); 18 | Q_INVOKABLE void mousePressed(const QPointF &position); 19 | Q_INVOKABLE void mouseReleased(const QPointF &position); 20 | Q_INVOKABLE void mouseMoved(const QPointF &position); 21 | Q_INVOKABLE void requestNewConnection(const QString &address); 22 | 23 | signals: 24 | void connected(); 25 | void disconnected(); 26 | void needUpdate(); 27 | 28 | private: 29 | inline void sendRemoteEvent(RemoteEvent::EventType type, const QPointF &position); 30 | 31 | Socket *m_socket; 32 | ImageProvider *m_provider; 33 | }; 34 | 35 | #endif // CONTROLLER_H 36 | -------------------------------------------------------------------------------- /src/tcp/protocol.cpp: -------------------------------------------------------------------------------- 1 | #include "protocol.h" 2 | #include "remoteevent.h" 3 | 4 | QDataStream& operator>>(QDataStream &in, BlockHeader &header) 5 | { 6 | in >> header.type 7 | >> header.dataSize; 8 | 9 | return in; 10 | } 11 | 12 | QDataStream& operator<<(QDataStream &out, const BlockHeader &header) 13 | { 14 | out << header.type 15 | << header.dataSize; 16 | 17 | return out; 18 | } 19 | 20 | QDataStream& operator>>(QDataStream &in, DataBlock &block) 21 | { 22 | in >> block.header 23 | >> block.data; 24 | 25 | return in; 26 | } 27 | 28 | QDataStream& operator<<(QDataStream &out, const DataBlock &block) 29 | { 30 | out << block.header 31 | << block.data; 32 | 33 | return out; 34 | } 35 | 36 | QDataStream &operator>>(QDataStream &in, RemoteEvent &block) 37 | { 38 | qint32 type; 39 | QPointF position; 40 | in >> type >> position; 41 | block.setType(RemoteEvent::EventType(type)); 42 | block.setPosition(position); 43 | 44 | return in; 45 | } 46 | 47 | QDataStream &operator<<(QDataStream &out, const RemoteEvent &block) 48 | { 49 | out << qint32(block.type()) 50 | << block.position(); 51 | 52 | return out; 53 | } 54 | 55 | QDebug operator<<(QDebug debug, const BlockHeader &header) 56 | { 57 | debug << "[type]: " << header.type << endl 58 | << "[data size]: " << header.dataSize << endl; 59 | 60 | return debug; 61 | } 62 | 63 | QDebug operator<<(QDebug debug, const DataBlock &block) 64 | { 65 | debug << "[type]: " << block.header.type << endl 66 | << "[data size]: " << block.header.dataSize << endl; 67 | 68 | return debug; 69 | } 70 | -------------------------------------------------------------------------------- /src/tcp/protocol.h: -------------------------------------------------------------------------------- 1 | #ifndef PROTOCOL_H 2 | #define PROTOCOL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define INFO_TYPE 0x01 9 | #define EVENT_TYPE 0x02 10 | #define SCREEN_TYPE 0x03 11 | 12 | struct BlockHeader 13 | { 14 | qint8 type; 15 | qint32 dataSize; 16 | 17 | BlockHeader() : type(0), dataSize(0) { } 18 | BlockHeader(qint8 t, qint32 s) : type(t), dataSize(s) { } 19 | 20 | bool isEmpty() const { 21 | return type == 0 && dataSize == 0; 22 | } 23 | 24 | void clear() { 25 | type = dataSize = 0; 26 | } 27 | 28 | int size() const { 29 | return sizeof(type) + sizeof(dataSize) + 4; //data为QByteArray会加4字节 30 | } 31 | }; 32 | 33 | struct DataBlock 34 | { 35 | BlockHeader header; 36 | QByteArray data; 37 | }; 38 | 39 | class RemoteEvent; 40 | 41 | extern QDataStream& operator>>(QDataStream &in, BlockHeader &header); 42 | extern QDataStream& operator<<(QDataStream &out, const BlockHeader &header); 43 | extern QDataStream& operator>>(QDataStream &in, DataBlock &block); 44 | extern QDataStream& operator<<(QDataStream &out, const DataBlock &block); 45 | extern QDataStream &operator>>(QDataStream &in, RemoteEvent &event); 46 | extern QDataStream &operator<<(QDataStream &out, const RemoteEvent &event); 47 | extern QDebug operator<<(QDebug debug, const BlockHeader &header); 48 | extern QDebug operator<<(QDebug debug, const DataBlock &block); 49 | 50 | #endif // PROTOCOL_H 51 | -------------------------------------------------------------------------------- /src/tcp/socket.cpp: -------------------------------------------------------------------------------- 1 | #include "remoteevent.h" 2 | #include "socket.h" 3 | 4 | Socket::Socket(QObject *parent) 5 | : QTcpSocket (parent) 6 | { 7 | connect(this, &QTcpSocket::readyRead, this, [this]() { 8 | m_recvData += readAll(); 9 | processRecvBlock(); 10 | }); 11 | } 12 | 13 | Socket::~Socket() 14 | { 15 | 16 | } 17 | 18 | void Socket::abort() 19 | { 20 | QTcpSocket::abort(); 21 | m_recvData.clear(); 22 | m_recvHeader.clear(); 23 | } 24 | 25 | void Socket::writeToSocket(const DataBlock &block) 26 | { 27 | QByteArray data; 28 | QDataStream out(&data, QIODevice::WriteOnly); 29 | out.setVersion(QDataStream::Qt_5_12); 30 | out << block; 31 | write(data); 32 | } 33 | 34 | void Socket::writeToSocket(const RemoteEvent &event) 35 | { 36 | QByteArray data; 37 | QDataStream out(&data, QIODevice::WriteOnly); 38 | out.setVersion(QDataStream::Qt_5_12); 39 | out << event; 40 | BlockHeader header = { EVENT_TYPE, data.size() }; 41 | DataBlock block = { header, data }; 42 | out.device()->seek(0); 43 | out << block; 44 | write(data); 45 | flush(); 46 | } 47 | 48 | void Socket::processRecvBlock() 49 | { 50 | if (m_recvHeader.isEmpty() && m_recvData.size() > 0) { 51 | BlockHeader header; 52 | QDataStream in(&m_recvData, QIODevice::ReadOnly); 53 | in.setVersion(QDataStream::Qt_5_12); 54 | in >> header; 55 | 56 | if (header.isEmpty()) 57 | return; 58 | 59 | m_recvHeader = header; 60 | m_recvData.remove(0, header.size()); 61 | } 62 | 63 | if (m_recvData.size() < m_recvHeader.dataSize) 64 | return; 65 | 66 | DataBlock block; 67 | block.header = m_recvHeader; 68 | block.data = m_recvData.left(m_recvHeader.dataSize); 69 | m_recvData = m_recvData.mid(m_recvHeader.dataSize); 70 | m_recvHeader.clear(); 71 | 72 | if (block.header.type == SCREEN_TYPE) { 73 | emit hasScreenData(block.data); 74 | } else if (block.header.type == EVENT_TYPE) { 75 | RemoteEvent event; 76 | QDataStream in(block.data); 77 | in.setVersion(QDataStream::Qt_5_12); 78 | in >> event; 79 | emit hasEventData(event); 80 | } 81 | 82 | if (m_recvData.size() > 0) //如果还有则继续处理 83 | processRecvBlock(); 84 | } 85 | -------------------------------------------------------------------------------- /src/tcp/socket.h: -------------------------------------------------------------------------------- 1 | #ifndef SOCKET_H 2 | #define SOCKET_H 3 | 4 | #include "protocol.h" 5 | #include 6 | 7 | class RemoteEvent; 8 | class Socket : public QTcpSocket 9 | { 10 | Q_OBJECT 11 | public: 12 | explicit Socket(QObject *parent = nullptr); 13 | ~Socket(); 14 | 15 | Q_INVOKABLE void abort(); 16 | Q_INVOKABLE void connectHost(const QHostAddress &host, quint16 port) { QTcpSocket::connectToHost(host, port); } 17 | Q_INVOKABLE void writeToSocket(const QByteArray &block) { write(block); } 18 | Q_INVOKABLE void writeToSocket(const DataBlock &block); 19 | Q_INVOKABLE void writeToSocket(const RemoteEvent &event); 20 | 21 | signals: 22 | void hasScreenData(const QByteArray &screenData); 23 | void hasEventData(const RemoteEvent &event); 24 | 25 | private: 26 | void processRecvBlock(); 27 | 28 | QByteArray m_recvData; 29 | BlockHeader m_recvHeader; 30 | }; 31 | 32 | #endif // SOCKET_H 33 | -------------------------------------------------------------------------------- /src/udp/connection.cpp: -------------------------------------------------------------------------------- 1 | #include "connection.h" 2 | #include "protocol.h" 3 | #include "remoteevent.h" 4 | 5 | Connection::Connection(QObject *parent) 6 | : QTcpSocket (parent) 7 | { 8 | connect(this, &QTcpSocket::readyRead, this, [this]() { 9 | m_recvData += readAll(); 10 | processRecvData(); 11 | }); 12 | } 13 | 14 | void Connection::writeToSocket(const RemoteEvent &event) 15 | { 16 | QByteArray data; 17 | QDataStream out(&data, QIODevice::WriteOnly); 18 | out.setVersion(QDataStream::Qt_5_12); 19 | out << event; 20 | write(data); 21 | flush(); 22 | } 23 | 24 | void Connection::processRecvData() 25 | { 26 | RemoteEvent event; 27 | QDataStream in(&m_recvData, QIODevice::ReadOnly); 28 | in.setVersion(QDataStream::Qt_5_12); 29 | in >> event; 30 | 31 | if (!event.isEmpty()) { 32 | m_recvData.remove(0, event.size()); 33 | emit hasEventData(event); 34 | } 35 | 36 | if (m_recvData.size() > 0) 37 | processRecvData(); 38 | } 39 | -------------------------------------------------------------------------------- /src/udp/connection.h: -------------------------------------------------------------------------------- 1 | #ifndef CONNECTION_H 2 | #define CONNECTION_H 3 | 4 | #include 5 | 6 | class RemoteEvent; 7 | class Connection : public QTcpSocket 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | Connection(QObject *parent = nullptr); 13 | 14 | Q_INVOKABLE void abort() { QTcpSocket::abort(); } 15 | Q_INVOKABLE void writeToSocket(const RemoteEvent &event); 16 | 17 | signals: 18 | void hasEventData(const RemoteEvent &event); 19 | 20 | private: 21 | void processRecvData(); 22 | 23 | QByteArray m_recvData; 24 | }; 25 | 26 | #endif // CONNECTION_H 27 | -------------------------------------------------------------------------------- /src/udp/controlled.cpp: -------------------------------------------------------------------------------- 1 | #include "controlled.h" 2 | #include "connection.h" 3 | #include "remoteevent.h" 4 | #include "systemapi.h" 5 | #include "socket.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | Controlled::Controlled(QObject *parent) 13 | : QTcpServer (parent) 14 | { 15 | listen(QHostAddress::Any, 43801); 16 | m_screenSocket = new Socket; 17 | QThread *thread = new QThread; 18 | connect(thread, &QThread::finished, thread, &QThread::deleteLater); 19 | m_screenSocket->moveToThread(thread); 20 | thread->start(); 21 | } 22 | 23 | Controlled::~Controlled() 24 | { 25 | 26 | } 27 | 28 | void Controlled::finish() 29 | { 30 | if (m_connection) 31 | m_connection->disconnectFromHost(); 32 | } 33 | 34 | void Controlled::processEvent(const RemoteEvent &ev) 35 | { 36 | QRectF screenRect = qApp->primaryScreen()->geometry(); 37 | QPointF localPos(ev.position().x() * screenRect.width(), 38 | ev.position().y() * screenRect.height()); 39 | 40 | switch (ev.type()) 41 | { 42 | case RemoteEvent::EventType::Pressed: 43 | SystemApi::mousePress(localPos); 44 | break; 45 | case RemoteEvent::EventType::Released: 46 | SystemApi::mouseRelease(localPos); 47 | break; 48 | case RemoteEvent::EventType::Moved: 49 | SystemApi::mouseMove(localPos); 50 | break; 51 | case RemoteEvent::EventType::KeyInput: 52 | break; 53 | default: 54 | break; 55 | } 56 | } 57 | 58 | void Controlled::timerEvent(QTimerEvent *event) 59 | { 60 | Q_UNUSED(event); 61 | if (m_screenSocket) { 62 | QBuffer buffer; 63 | buffer.open(QIODevice::WriteOnly); 64 | QPixmap pixmap = SystemApi::grabScreen(); 65 | pixmap.save(&buffer, "jpg", -1); 66 | QMetaObject::invokeMethod(m_screenSocket, "writeToSocket", Q_ARG(QByteArray, buffer.data())); 67 | } 68 | } 69 | 70 | void Controlled::incomingConnection(qintptr socketDescriptor) 71 | { 72 | if (!m_connection) { 73 | m_connection = new Connection(this); 74 | connect(m_connection, &Connection::stateChanged, this, [this](QAbstractSocket::SocketState socketState) { 75 | switch (socketState) 76 | { 77 | case QAbstractSocket::ConnectedState: 78 | emit connected(); 79 | break; 80 | default: 81 | break; 82 | } 83 | }); 84 | connect(m_connection, &Connection::disconnected, this, [this]() { 85 | m_connection->deleteLater(); 86 | m_connection = nullptr; 87 | QMetaObject::invokeMethod(m_screenSocket, "finish"); 88 | killTimer(m_timerId); 89 | m_timerId = 0; 90 | emit disconnected(); 91 | }); 92 | connect(m_connection, &Connection::hasEventData, this, [this](const RemoteEvent &event) { 93 | processEvent(event); 94 | }); 95 | m_connection->setSocketDescriptor(socketDescriptor); 96 | QMetaObject::invokeMethod(m_screenSocket, "setDestAddr", 97 | Q_ARG(QHostAddress, QHostAddress(m_connection->peerAddress()))); 98 | 99 | if (!m_timerId) 100 | m_timerId = startTimer(std::chrono::milliseconds(30)); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/udp/controlled.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTROLLED_H 2 | #define CONTROLLED_H 3 | 4 | #include 5 | #include 6 | 7 | class Socket; 8 | class RemoteEvent; 9 | class Connection; 10 | class Controlled : public QTcpServer 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit Controlled(QObject *parent = nullptr); 16 | ~Controlled(); 17 | 18 | Q_INVOKABLE void finish(); 19 | 20 | signals: 21 | void connected(); 22 | void disconnected(); 23 | 24 | public slots: 25 | void processEvent(const RemoteEvent &ev); 26 | 27 | protected: 28 | void timerEvent(QTimerEvent *event); 29 | void incomingConnection(qintptr socketDescriptor); 30 | 31 | private: 32 | int m_timerId = 0; 33 | Socket *m_screenSocket = nullptr; 34 | Connection *m_connection = nullptr; 35 | }; 36 | 37 | #endif // CONTROLLED_H 38 | -------------------------------------------------------------------------------- /src/udp/controller.cpp: -------------------------------------------------------------------------------- 1 | #include "controller.h" 2 | #include "connection.h" 3 | #include "imageprovider.h" 4 | #include "networkapi.h" 5 | #include "socket.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | Controller::Controller(QObject *parent) 12 | : QObject(parent) 13 | { 14 | m_provider = new ImageProvider; 15 | m_socket = new Socket; 16 | m_socket->bind(QHostAddress::Any, 43800); 17 | QThread *thread = new QThread; 18 | connect(thread, &QThread::finished, thread, &QThread::deleteLater); 19 | connect(m_socket, &Socket::hasScreenData, this, [this](const QByteArray &screenData) { 20 | QPixmap pixmap; 21 | pixmap.loadFromData(screenData); 22 | m_provider->setPixmap(pixmap); 23 | emit needUpdate(); 24 | }); 25 | m_socket->moveToThread(thread); 26 | thread->start(); 27 | 28 | m_connection = new Connection(this); 29 | connect(m_connection, &Connection::connected, this, &Controller::connected); 30 | connect(m_connection, &Connection::disconnected, this, &Controller::disconnected); 31 | } 32 | 33 | void Controller::finish() 34 | { 35 | m_connection->abort(); 36 | QMetaObject::invokeMethod(m_socket, "finish"); 37 | } 38 | 39 | void Controller::mousePressed(const QPointF &position) 40 | { 41 | sendRemoteEvent(RemoteEvent::EventType::Pressed, position); 42 | } 43 | 44 | void Controller::mouseReleased(const QPointF &position) 45 | { 46 | sendRemoteEvent(RemoteEvent::EventType::Released, position); 47 | } 48 | 49 | void Controller::mouseMoved(const QPointF &position) 50 | { 51 | sendRemoteEvent(RemoteEvent::EventType::Moved, position); 52 | } 53 | 54 | void Controller::requestNewConnection(const QString &address) 55 | { 56 | m_connection->abort(); 57 | QHostAddress hostAddress(address); 58 | //有效且不为本机地址 59 | if (!hostAddress.isNull() && !NetworkApi::isLocalAddress(hostAddress)){ 60 | m_connection->connectToHost(address, 43801); 61 | QMetaObject::invokeMethod(m_socket, "setDestAddr", Q_ARG(QHostAddress, QHostAddress(address))); 62 | } 63 | } 64 | 65 | void Controller::sendRemoteEvent(RemoteEvent::EventType type, const QPointF &position) 66 | { 67 | RemoteEvent event(type, position); 68 | m_connection->writeToSocket(event); 69 | } 70 | -------------------------------------------------------------------------------- /src/udp/controller.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTROLLER_H 2 | #define CONTROLLER_H 3 | 4 | #include "remoteevent.h" 5 | #include 6 | 7 | class Socket; 8 | class Connection; 9 | class ImageProvider; 10 | class Controller : public QObject 11 | { 12 | Q_OBJECT 13 | public: 14 | explicit Controller(QObject *parent = nullptr); 15 | 16 | ImageProvider* getImageProvider() { return m_provider; } 17 | 18 | Q_INVOKABLE void finish(); 19 | Q_INVOKABLE void mousePressed(const QPointF &position); 20 | Q_INVOKABLE void mouseReleased(const QPointF &position); 21 | Q_INVOKABLE void mouseMoved(const QPointF &position); 22 | Q_INVOKABLE void requestNewConnection(const QString &address); 23 | 24 | signals: 25 | void connected(); 26 | void disconnected(); 27 | void needUpdate(); 28 | 29 | private: 30 | inline void sendRemoteEvent(RemoteEvent::EventType type, const QPointF &position); 31 | 32 | Socket *m_socket; 33 | Connection *m_connection; 34 | ImageProvider *m_provider; 35 | }; 36 | 37 | #endif // CONTROLLER_H 38 | -------------------------------------------------------------------------------- /src/udp/protocol.cpp: -------------------------------------------------------------------------------- 1 | #include "protocol.h" 2 | #include "remoteevent.h" 3 | 4 | QDataStream &operator>>(QDataStream &in, DataBlock &block) 5 | { 6 | in >> block.blockSize 7 | >> block.blockIndex 8 | >> block.blockNum 9 | >> block.data; 10 | 11 | return in; 12 | } 13 | 14 | QDataStream &operator<<(QDataStream &out, const DataBlock &block) 15 | { 16 | out << block.blockSize 17 | << block.blockIndex 18 | << block.blockNum 19 | << block.data; 20 | 21 | return out; 22 | } 23 | 24 | QDataStream &operator>>(QDataStream &in, RemoteEvent &block) 25 | { 26 | qint32 type; 27 | QPointF position; 28 | in >> type >> position; 29 | block.setType(RemoteEvent::EventType(type)); 30 | block.setPosition(position); 31 | 32 | return in; 33 | } 34 | 35 | QDataStream &operator<<(QDataStream &out, const RemoteEvent &block) 36 | { 37 | out << qint32(block.type()) 38 | << block.position(); 39 | 40 | return out; 41 | } 42 | 43 | QDebug operator<<(QDebug debug, const DataBlock &block) 44 | { 45 | debug << "[blockSize]: " << block.blockSize << endl 46 | << "[blockIndex]: " << block.blockIndex << endl 47 | << "[blockNum]: " << block.blockNum << endl 48 | << "[data size]: " << block.data.size() << endl; 49 | 50 | return debug; 51 | } 52 | -------------------------------------------------------------------------------- /src/udp/protocol.h: -------------------------------------------------------------------------------- 1 | #ifndef PROTOCOL_UDP_H 2 | #define PROTOCOL_UDP_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct DataBlock 9 | { 10 | qint32 blockSize; //总大小 11 | qint32 blockIndex; //索引 12 | qint32 blockNum; //被分成的块数 13 | QByteArray data; //块数据 14 | 15 | DataBlock() 16 | : blockSize(0), blockIndex(0) 17 | , blockNum(0), data(QByteArray()) { } 18 | 19 | int size() const { 20 | return int(sizeof (blockIndex)) + 21 | int(sizeof (blockSize)) + 22 | int(sizeof (blockNum)) + 23 | data.size() + 4; 24 | } 25 | }; 26 | 27 | class RemoteEvent; 28 | 29 | extern QDataStream &operator>>(QDataStream &in, DataBlock &block); 30 | extern QDataStream &operator<<(QDataStream &out, const DataBlock &block); 31 | extern QDataStream &operator>>(QDataStream &in, RemoteEvent &event); 32 | extern QDataStream &operator<<(QDataStream &out, const RemoteEvent &event); 33 | extern QDebug operator<<(QDebug debug, const DataBlock &block); 34 | 35 | #endif // PROTOCOL_UDP_H 36 | -------------------------------------------------------------------------------- /src/udp/socket.cpp: -------------------------------------------------------------------------------- 1 | #include "remoteevent.h" 2 | #include "socket.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | static const int maxBlockSize = 4096; 9 | 10 | Socket::Socket(QObject *parent) 11 | : QUdpSocket (parent) 12 | { 13 | connect(this, &QUdpSocket::readyRead, this, &Socket::processRecvData); 14 | } 15 | 16 | Socket::~Socket() 17 | { 18 | 19 | } 20 | 21 | void Socket::finish() 22 | { 23 | m_destAddr = QHostAddress(); 24 | } 25 | 26 | void Socket::writeToSocket(const QByteArray &d) 27 | { 28 | static QTime time = QTime::currentTime(); 29 | static int frame = 0; 30 | if (time.msecsTo(QTime::currentTime()) > 1000) { 31 | qDebug().nospace() << "发送速率:" << frame << "/s"; 32 | frame = 0; 33 | time = QTime::currentTime(); 34 | } 35 | 36 | if (!m_destAddr.isNull()) { 37 | int currentIndex = 0; 38 | int blockOffset = 0; 39 | int blockSize = d.size(); 40 | int blockNum = blockSize / maxBlockSize; 41 | int last = blockSize % maxBlockSize; 42 | if (last != 0) blockNum++; 43 | 44 | QByteArray data; 45 | QDataStream out(&data, QIODevice::WriteOnly); 46 | out.setVersion(QDataStream::Qt_5_12); 47 | 48 | while (currentIndex < blockNum) { 49 | DataBlock block; 50 | block.blockIndex = currentIndex + 1; 51 | block.blockNum = blockNum; 52 | block.blockSize = blockSize; 53 | block.data = d.mid(blockOffset, maxBlockSize); 54 | 55 | out.device()->seek(0); 56 | out << block; 57 | writeDatagram(data, m_destAddr, 43800); 58 | QThread::usleep(1); //避免发送过快 59 | 60 | data.clear(); 61 | blockOffset += maxBlockSize; 62 | currentIndex++; 63 | } 64 | frame++; 65 | } 66 | } 67 | 68 | void Socket::processRecvData() 69 | { 70 | static int currentIndex = 1; 71 | static int blockSize = 0; 72 | static QByteArray recvData; 73 | 74 | while (hasPendingDatagrams()) { 75 | QNetworkDatagram datagram = receiveDatagram(); 76 | QByteArray data = datagram.data(); 77 | 78 | DataBlock block; 79 | QDataStream in(&data, QIODevice::ReadOnly); 80 | in >> block; 81 | 82 | if (currentIndex == 1) 83 | recvData.resize(block.blockSize); 84 | 85 | if (currentIndex == block.blockIndex) { 86 | currentIndex++; 87 | blockSize += block.data.size(); 88 | recvData.insert((block.blockIndex - 1) * maxBlockSize, block.data); 89 | 90 | if (block.blockIndex == block.blockNum && blockSize == block.blockSize) { 91 | emit hasScreenData(recvData); 92 | 93 | currentIndex = 1; 94 | blockSize = 0; 95 | recvData.clear(); 96 | } 97 | } else { 98 | currentIndex = 1; 99 | blockSize = 0; 100 | recvData.clear(); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/udp/socket.h: -------------------------------------------------------------------------------- 1 | #ifndef SOCKET_UDP_H 2 | #define SOCKET_UDP_H 3 | 4 | #include "protocol.h" 5 | #include 6 | 7 | class RemoteEvent; 8 | class Socket : public QUdpSocket 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | Socket(QObject *parent = nullptr); 14 | ~Socket(); 15 | 16 | QHostAddress destAddr() const { return m_destAddr; } 17 | Q_INVOKABLE void setDestAddr(const QHostAddress &addr) { m_destAddr = addr; } 18 | 19 | Q_INVOKABLE void finish(); 20 | Q_INVOKABLE void writeToSocket(const QByteArray &d); 21 | 22 | signals: 23 | void hasScreenData(const QByteArray &screenData); 24 | 25 | private slots: 26 | void processRecvData(); 27 | 28 | private: 29 | QHostAddress m_destAddr; 30 | }; 31 | 32 | #endif // SOCKET_UDP_H 33 | --------------------------------------------------------------------------------