背景
为什么会选择Qt+mqtt来实现呢?
其实最开始的时候是用electron+webrtc+srs实现的,但是由于webrtc需要交换协议,而electron中的webrtc获取协议内容是通过chromium做的,这块就很难控制,最关键的问题是,electron的包很难下的动,所以就放弃了electron。
后来调研了javafx的方案,基于javaffx+javacv+Zlmediakit的方案,这里的问题是图片合并推流的时候,会形成很大的延迟,除非去优化Zlmediakit ,但是这个是一个很小的功能,不光要考虑开发成本,还需要考虑运维成本。并且,javafx的托盘中文乱码,以及打包成exe的问题,不太好处理,反正我这边是没成功。wix的环境明明安装了,就是说找不到。
flutter的windows方案也简单调研过,但是考虑到这里需要进行桌面截屏,而flutter目前方案还不是很成熟,也放弃了。
最终还是决定用qt的方案,由于需求,只是屏幕共享,之前也调研过远程桌面控制的方案,其实也是基于桌面截屏方案,所以,这里就直接用mqtt的方案来做。
Mqtt
Qt 的环境默认是没有mqtt的,这里我们需要自己编译,并加入到环境中
切换到与本地Qt版本号一致的分支,进行构建

构建完成之后,只需要将相关的文件同步赋复制到Qt的环境下就可以。
主要有如下几个目录
- bin
- include
- lib
- mkspecs
- modules
注意:bin 文件夹下,只需要复制dll文件即可
程序代码
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QLabel>
#include <QMainWindow>
#include <QMenu>
#include <QMqttClient>
#include <QScreen>
#include <QSettings>
#include <QSystemTrayIcon>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
QString ipV4();
void connectMqtt();
QSettings *settings;
void screenMonitor();
protected:
void closeEvent(QCloseEvent *event) override;
private:
Ui::MainWindow *ui;
QScreen *screen;
QLabel *imageLabel;
QSystemTrayIcon *trayIcon;
QMenu *trayMenu;
QString ipv4;
QMqttClient *mqttClient;
bool monitoring;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QSystemTrayIcon>
#include <QMenu>
#include <QCloseEvent>
#include <QDebug>
#include <QHostInfo>
#include <QLabel>
#include <QCoreApplication>
#include <QGuiApplication>
#include <QTimer>
#include <QBuffer>
#include <QMqttTopicName>
#include <QMqttClient>
#include <QImage>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow) {
ui->setupUi(this);
settings = new QSettings("config.ini", QSettings::IniFormat);
qDebug() << "mqtt hostname:" << settings->value("mqtt/hostname").toString();
ipv4 = this->ipV4();
mqttClient = new QMqttClient(this);
this->setWindowTitle("屏幕共享:" + ipv4);
imageLabel = new QLabel(this);
imageLabel->setScaledContents(true);
// 设置 QLabel 的大小策略为自动调整大小
imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
// 设置 QLabel 在布局中的对齐方式
imageLabel->setAlignment(Qt::AlignCenter);
trayIcon = new QSystemTrayIcon(this);
trayIcon->setIcon(QIcon(":/resources/assets/logo.png"));
trayIcon->setToolTip("屏幕共享");
trayMenu = new QMenu(this);
trayMenu->addAction("打开窗口", this, [&]() {
this->show();
});
trayMenu->addAction("开始", this, [&]() {
monitoring = true;
});
trayMenu->addAction("停止", this, [&]() {
monitoring = false;
});
trayMenu->addAction("退出", this, [&]() {
QCoreApplication::quit();
});
trayIcon->setContextMenu(trayMenu);
trayIcon->show();
screen = QGuiApplication::primaryScreen();
QPixmap screenshot = screen->grabWindow(0);
imageLabel->setPixmap(screenshot);
screenshot.scaled(this->size(), Qt::KeepAspectRatio);
this->setCentralWidget(imageLabel);
this->setMinimumWidth(920);
this->setMinimumHeight(540);
connectMqtt();
screenMonitor();
}
MainWindow::~MainWindow() {
delete ui;
}
void MainWindow::closeEvent(QCloseEvent *event) {
if (trayIcon->isVisible()) {
hide();
event->ignore();
} else {
event->accept();
}
}
void MainWindow::screenMonitor() {
QTimer *timer = new QTimer(this);
QObject::connect(timer, &QTimer::timeout, [&]() {
// 截取屏幕内容L
QPixmap screenshot = screen->grabWindow(0);
// 设置 QLabel 的图片
imageLabel->setPixmap(screenshot);
if (monitoring) {
QByteArray byteArray;
QBuffer buffer(&byteArray);
buffer.open(QIODevice::WriteOnly);
QImage image = screenshot.toImage();
qDebug() << "origin size" << image.sizeInBytes();
int ratio = settings->value("image/ratio").toInt();
screenshot.save(&buffer, "JPEG", (ratio > 0 ? ratio : 20)); // 可替换为其他图片格式,如 "PNG"
QString base64 = byteArray.toBase64();
//qDebug() << base64;
QMqttTopicName topic("desktop/" + ipv4);
qDebug() << mqttClient->state();
qDebug() << base64.toUtf8().size();
if (mqttClient->state() == QMqttClient::Connected) {
mqttClient->publish(topic, base64.toUtf8());
}
}
});
int interval = settings->value("timer/interval").toInt();
timer->setInterval(interval > 0 ? interval : 200);
timer->start();
}
QString MainWindow::ipV4() {
QString hostName = QHostInfo::localHostName();
// 根据主机名获取主机信息
QHostInfo hostInfo = QHostInfo::fromName(hostName);
// 获取IP地址列表
QList <QHostAddress> addressList = hostInfo.addresses();
// 遍历IP地址列表
foreach(
const QHostAddress &address, addressList) {
// 判断是否是IPv4地址
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
qDebug() << "IPv4 Address:" << address.toString();
return address.toString();
}
}
return NULL;
}
void MainWindow::connectMqtt() {
mqttClient->setClientId("desktop_" + ipv4);
mqttClient->setHostname(settings->value("mqtt/hostname").toString());
mqttClient->setPort(settings->value("mqtt/port").toInt());
mqttClient->setUsername(settings->value("mqtt/username").toString());
mqttClient->setPassword(settings->value("mqtt/password").toString());
mqttClient->setAutoKeepAlive(true);
mqttClient->connectToHost();
QObject::connect(mqttClient, &QMqttClient::connected, [&]() {
qDebug() << "连接成功";
});
}
前端使用react + mqttjs 展示图片即可
import {useEffect, useState} from "react";
import * as mqtt from "mqtt";
import {Image} from "antd";
export const IndexPage = () => {
const [client, setClient] = useState<mqtt.MqttClient>()
const [image,setImage] = useState<any>()
useEffect(() => {
const mqttClient = mqtt.connect("mqtt://localhost:8083/mqtt")
mqttClient.on("connect", () => {
mqttClient.subscribe("desktop/192.168.247.1", (err) => {
});
});
mqttClient.on('message',(topic, payload, packet)=>{
setImage(`data:image/jpeg;base64,${payload}`);
})
setClient(mqttClient)
}, [])
return <>
<Image src={image}/>
</>
}
效果
左侧是前端获得的效果,右侧是屏幕捕捉的效果
部署
配置windowdeploy的环境变量

设置好部署文件夹

执行部署

然后在目标文件夹下就会看到生成的exe文件

通过windeployqt6 自动复制需要的依赖文件

