1. 增加了跨平台的截屏与系统信息获取工具模块
2. 增加了对于服务端截屏请求的处理类
This commit is contained in:
Vendored
+4
@@ -11,6 +11,10 @@
|
|||||||
#include <random>
|
#include <random>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
#ifndef M_PI
|
||||||
|
# define M_PI 3.14159265358979323846
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace AutoGUI {
|
namespace AutoGUI {
|
||||||
|
|
||||||
// 内部辅助函数
|
// 内部辅助函数
|
||||||
|
|||||||
@@ -9,3 +9,31 @@
|
|||||||
* 1. 处理来自服务端的数据,分发并执行
|
* 1. 处理来自服务端的数据,分发并执行
|
||||||
* 2. 完成非阻塞的事件循环处理,构建业务状态机
|
* 2. 完成非阻塞的事件循环处理,构建业务状态机
|
||||||
*/
|
*/
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class AppCore final : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(AppCore) // 禁用拷贝
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* 构造函数私有化
|
||||||
|
* @param parent
|
||||||
|
*/
|
||||||
|
explicit AppCore(QObject *parent = nullptr); // 并不将本模块挂在对象树当中,因为本模块为单例类,内存自行管理
|
||||||
|
|
||||||
|
static QScopedPointer<AppCore> m_instance; // 单例类
|
||||||
|
static QMutex m_mutex;
|
||||||
|
private slots:
|
||||||
|
// 业务接收槽函数
|
||||||
|
|
||||||
|
public:
|
||||||
|
// 单例访问点
|
||||||
|
static AppCore *getInstance();
|
||||||
|
// 显式销毁
|
||||||
|
static void destroy();
|
||||||
|
|
||||||
|
~AppCore() override;
|
||||||
|
|
||||||
|
};
|
||||||
@@ -3,6 +3,9 @@
|
|||||||
#include <QtWidgets/QWidget>
|
#include <QtWidgets/QWidget>
|
||||||
#include <QOpenGLWidget>
|
#include <QOpenGLWidget>
|
||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
class GLCore final : public QOpenGLWidget
|
class GLCore final : public QOpenGLWidget
|
||||||
{
|
{
|
||||||
@@ -81,7 +84,6 @@ private:
|
|||||||
bool isRightPressed; /// 鼠标右键是否按下
|
bool isRightPressed; /// 鼠标右键是否按下
|
||||||
QPoint currentPos; /// 当前鼠标位置
|
QPoint currentPos; /// 当前鼠标位置
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#include <windows.h>
|
|
||||||
private:
|
private:
|
||||||
HWND hwnd; // Windows窗口句柄
|
HWND hwnd; // Windows窗口句柄
|
||||||
void setWindowTransparentForMouse(bool transparent);
|
void setWindowTransparentForMouse(bool transparent);
|
||||||
|
|||||||
@@ -3,3 +3,52 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "AppCore.h"
|
#include "AppCore.h"
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include "AudioDataHandle.h"
|
||||||
|
#include "AutoAgentHandle.h"
|
||||||
|
#include "ScreenShotReqDataHandle.h"
|
||||||
|
|
||||||
|
// 初始化静态成员
|
||||||
|
QScopedPointer<AppCore> AppCore::m_instance;
|
||||||
|
QMutex AppCore::m_mutex;
|
||||||
|
|
||||||
|
// 单例实现 (QScopedPointer + Mutex)
|
||||||
|
AppCore* AppCore::getInstance()
|
||||||
|
{
|
||||||
|
if (m_instance.isNull()) {
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
if (m_instance.isNull()) {
|
||||||
|
// 使用 reset 创建实例,因为构造函数是私有的
|
||||||
|
m_instance.reset(new AppCore());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m_instance.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppCore::destroy()
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
if (!m_instance.isNull()) {
|
||||||
|
m_instance.reset(); // 这会触发析构函数
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AppCore::AppCore(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
// 初始化业务解析单例
|
||||||
|
AudioDataHandle::getInstance();
|
||||||
|
AutoAgentHandle::getInstance();
|
||||||
|
ScreenShotReqDataHandle::getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
AppCore::~AppCore()
|
||||||
|
{
|
||||||
|
// 析构业务解析单例
|
||||||
|
ScreenShotReqDataHandle::destroy();
|
||||||
|
AutoAgentHandle::destroy(); // 显式销毁
|
||||||
|
AudioDataHandle::destroy();
|
||||||
|
|
||||||
|
|
||||||
|
qDebug() << "AppCore destroyed";
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "DataTransferObjectBase.h"
|
#include "DataTransferObjectBase.h"
|
||||||
#include "AudioDataTransferObject.h"
|
#include "AudioDataTransferObject.h"
|
||||||
#include "AutoAgentDataObject.h"
|
#include "AutoAgentDataObject.h"
|
||||||
|
#include "ScreenShotDataTransferObject.h"
|
||||||
/**
|
/**
|
||||||
* NetworkDO
|
* NetworkDO
|
||||||
*/
|
*/
|
||||||
@@ -54,8 +55,9 @@ public:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
// 业务接收信号
|
// 业务接收信号
|
||||||
void audioPacketReceived(const AudioDataTransferObject& packet); // 音频数据准备完成信号
|
void audioPacketReceived(const AudioDataTransferObject& packet); // 音频数据准备完成信号
|
||||||
void autoAgentPacketReceived(const AutoAgentDataObject& packet); // 自动代理数据包接收信号
|
void autoAgentPacketReceived(const AutoAgentDataObject& packet); // 自动代理数据包接收信号
|
||||||
|
void screenShotPacketReceived(const ScreenShotDataTransferObject& packet); // 截图数据包接收信号
|
||||||
|
|
||||||
void errorOccurred(const QString& errorMsg); // 错误信号
|
void errorOccurred(const QString& errorMsg); // 错误信号
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
//
|
||||||
|
// Created by misaki on 2026/2/1.
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据传输对象 (DTO) 定义
|
||||||
|
* ScreenShotDataTransferObject
|
||||||
|
* 与Yosuga_server中的是对等DTO
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <QObject>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonValue>
|
||||||
|
#include "DataTransferObjectBase.h"
|
||||||
|
// 前向声明,减少依赖
|
||||||
|
class QJsonObject;
|
||||||
|
class ScreenShotDataTransferObject final : public DataTransferObjectBase{
|
||||||
|
public:
|
||||||
|
// 构造函数(带默认值)
|
||||||
|
explicit ScreenShotDataTransferObject(QString owner = "client",
|
||||||
|
bool isSuccess = true,
|
||||||
|
QString realtimeScreenShot = "",
|
||||||
|
int width = 0, int height = 0,
|
||||||
|
QString describeInfo = "", QString LLMResponse = ""
|
||||||
|
);
|
||||||
|
// 静态工厂方法
|
||||||
|
static ScreenShotDataTransferObject fromJson(const QJsonObject& json);
|
||||||
|
|
||||||
|
[[nodiscard]] QString type() const override { return "screenshot_req"; }
|
||||||
|
|
||||||
|
// 序列化
|
||||||
|
[[nodiscard]] QJsonObject toJson() const override; // 通过多态即可统一调用方式
|
||||||
|
|
||||||
|
// 链式调用设置
|
||||||
|
ScreenShotDataTransferObject& setData(const QString& key, const QJsonValue& value) override;
|
||||||
|
|
||||||
|
[[nodiscard]] QString owner() const { return m_owner; }
|
||||||
|
[[nodiscard]] bool isSuccess() const { return m_isSuccess; }
|
||||||
|
[[nodiscard]] QString realtimeScreenShot() const { return m_realtimeScreenShot; }
|
||||||
|
[[nodiscard]] int width() const { return m_width; }
|
||||||
|
[[nodiscard]] int height() const { return m_height; }
|
||||||
|
[[nodiscard]] QString describeInfo() const { return m_describeInfo; }
|
||||||
|
[[nodiscard]] QString LLMResponse() const { return m_LLMResponse; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_owner; /// 数据的拥有者(server or client)
|
||||||
|
bool m_isSuccess; /// 截图是否成功
|
||||||
|
QString m_realtimeScreenShot; /// 客户端设备的实时截图数据(base64)
|
||||||
|
int m_width; /// 截图宽度 非必要字段
|
||||||
|
int m_height; /// 截图高度 非必要字段
|
||||||
|
QString m_describeInfo; /// 设备的描述信息(告知模型以做出更加准确的判断) 非必要字段
|
||||||
|
QString m_LLMResponse; /// LLM的响应结果(由服务端发送时携带)
|
||||||
|
};
|
||||||
@@ -65,13 +65,16 @@ void NetworkDO::onDataReceived(const QString& type, const QJsonObject& data)
|
|||||||
// 根据类型分发数据包
|
// 根据类型分发数据包
|
||||||
// 为什么分发做在这里,而不是统一数据再去分发,如果不在这里做分发通知,分开发信号,而使用统一的信号
|
// 为什么分发做在这里,而不是统一数据再去分发,如果不在这里做分发通知,分开发信号,而使用统一的信号
|
||||||
// 如果有多个观察者,让观察者自动识别数据包,这会导致信号广播,容易引起性能问题(因为这里依赖的是Qt的信号与槽机制)
|
// 如果有多个观察者,让观察者自动识别数据包,这会导致信号广播,容易引起性能问题(因为这里依赖的是Qt的信号与槽机制)
|
||||||
// TODO: 在此处使用工厂模式,根据type内容快速创建对应的对象
|
// TODO: 考虑在此处使用工厂模式,根据type内容快速创建对应的对象
|
||||||
if (type == "audio_data") {
|
if (type == "audio_data") {
|
||||||
emit audioPacketReceived(AudioDataTransferObject::fromJson(data)); // 构造并发送音频对象
|
emit audioPacketReceived(AudioDataTransferObject::fromJson(data)); // 构造并发送音频对象
|
||||||
}
|
}
|
||||||
else if (type == "auto_agent") {
|
else if (type == "auto_agent") {
|
||||||
emit autoAgentPacketReceived(AutoAgentDataObject::fromJson(data));
|
emit autoAgentPacketReceived(AutoAgentDataObject::fromJson(data));
|
||||||
}
|
}
|
||||||
|
else if (type == "screenshot_req") {
|
||||||
|
emit screenShotPacketReceived(ScreenShotDataTransferObject::fromJson(data));
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
qWarning() << "[NetworkDO] Received unknown type:" << type;
|
qWarning() << "[NetworkDO] Received unknown type:" << type;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
//
|
||||||
|
// Created by misaki on 2026/2/1.
|
||||||
|
//
|
||||||
|
#include "ScreenShotDataTransferObject.h"
|
||||||
|
#include <QJsonValue>
|
||||||
|
#include <utility>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
// 构造函数实现(初始化列表)
|
||||||
|
ScreenShotDataTransferObject::ScreenShotDataTransferObject(QString owner,
|
||||||
|
bool isSuccess,
|
||||||
|
QString realtimeScreenShot,
|
||||||
|
int width, int height,
|
||||||
|
QString describeInfo, QString LLMResponse)
|
||||||
|
: m_owner(std::move(owner))
|
||||||
|
, m_isSuccess(isSuccess)
|
||||||
|
, m_realtimeScreenShot(std::move(realtimeScreenShot))
|
||||||
|
, m_width(width)
|
||||||
|
, m_height(height)
|
||||||
|
, m_describeInfo(std::move(describeInfo))
|
||||||
|
, m_LLMResponse(std::move(LLMResponse))
|
||||||
|
{}
|
||||||
|
|
||||||
|
// 静态工厂方法:从 JSON 反序列化
|
||||||
|
ScreenShotDataTransferObject ScreenShotDataTransferObject::fromJson(const QJsonObject& json) {
|
||||||
|
// 逐个字段读取,不存在则用默认值
|
||||||
|
const QString owner = json.value("Owner").toString("client");
|
||||||
|
const bool isSuccess = json.value("isSuccess").toBool(false);
|
||||||
|
const QString realtimeScreenShot = json.value("RealTimeScreenShot").toString();
|
||||||
|
const int width = json.value("Width").toInt(0);
|
||||||
|
const int height = json.value("Height").toInt(0);
|
||||||
|
const QString describeInfo = json.value("DescribeInfo").toString();
|
||||||
|
const QString LLMResponse = json.value("LLMResponse").toString();
|
||||||
|
|
||||||
|
// 调用构造函数创建对象
|
||||||
|
return ScreenShotDataTransferObject(owner, isSuccess, realtimeScreenShot,
|
||||||
|
width, height, describeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序列化为 JSON
|
||||||
|
QJsonObject ScreenShotDataTransferObject::toJson() const {
|
||||||
|
QJsonObject json;
|
||||||
|
json["Owner"] = m_owner;
|
||||||
|
json["isSuccess"] = m_isSuccess;
|
||||||
|
json["RealTimeScreenShot"] = m_realtimeScreenShot;
|
||||||
|
json["Width"] = m_width;
|
||||||
|
json["Height"] = m_height;
|
||||||
|
json["DescribeInfo"] = m_describeInfo;
|
||||||
|
json["LLMResponse"] = m_LLMResponse;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 链式设置
|
||||||
|
ScreenShotDataTransferObject& ScreenShotDataTransferObject::setData(const QString& key,
|
||||||
|
const QJsonValue& value) {
|
||||||
|
if (key == "Owner") {
|
||||||
|
m_owner = value.toString();
|
||||||
|
} else if (key == "isSuccess") {
|
||||||
|
m_isSuccess = value.toBool();
|
||||||
|
} else if (key == "RealTimeScreenShot") {
|
||||||
|
m_realtimeScreenShot = value.toString();
|
||||||
|
} else if (key == "Width") {
|
||||||
|
m_width = value.toInt();
|
||||||
|
} else if (key == "Height") {
|
||||||
|
m_height = value.toInt();
|
||||||
|
} else if (key == "DescribeInfo") {
|
||||||
|
m_describeInfo = value.toString();
|
||||||
|
} else if (key == "LLMResponse") {
|
||||||
|
m_LLMResponse = value.toString();
|
||||||
|
} else {
|
||||||
|
qWarning() << "Unknown key:" << key << "for ScreenShotDataTransferObject";
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this; // 返回自身引用,支持链式调用
|
||||||
|
}
|
||||||
@@ -53,7 +53,7 @@ void AudioInput::setAudioSettings(const int rate, const int channels)
|
|||||||
{
|
{
|
||||||
m_format.setSampleRate(rate);
|
m_format.setSampleRate(rate);
|
||||||
m_format.setChannelCount(channels);
|
m_format.setChannelCount(channels);
|
||||||
// 重要:Qt6 默认可能是 float,为了生成标准 WAV 且方便计算 RMS,强制设为 Int16
|
// 为了生成标准 WAV 且方便计算 RMS,强制设为 Int16
|
||||||
m_format.setSampleFormat(QAudioFormat::Int16);
|
m_format.setSampleFormat(QAudioFormat::Int16);
|
||||||
|
|
||||||
// 检查设备是否支持该格式,不支持则使用最接近的
|
// 检查设备是否支持该格式,不支持则使用最接近的
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class AudioDataHandle final : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(AudioDataHandle) // 禁用拷贝
|
Q_DISABLE_COPY(AudioDataHandle) // 禁用拷贝
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* 构造函数私有化
|
* 构造函数私有化
|
||||||
* @param parent
|
* @param parent
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// Created by misaki on 2026/2/1.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#include <QObject>
|
||||||
|
#include <QMutex>
|
||||||
|
#include "ScreenShotDataTransferObject.h"
|
||||||
|
class ScreenShotReqDataHandle final : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(ScreenShotReqDataHandle) // 禁用拷贝
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* 构造函数私有化
|
||||||
|
* @param parent
|
||||||
|
*/
|
||||||
|
explicit ScreenShotReqDataHandle(QObject *parent = nullptr); // 并不将本模块挂在对象树当中,因为本模块为单例类,内存自行管理
|
||||||
|
|
||||||
|
static QScopedPointer<ScreenShotReqDataHandle> m_instance; // 单例类
|
||||||
|
static QMutex m_mutex;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
// 业务接收槽函数,当获取到截图数据包时,进行解析并处理
|
||||||
|
void onScreenShotPacketReceived(const ScreenShotDataTransferObject& packet) const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
// 发送截图处理完成的信号,供界面显示使用
|
||||||
|
void screenShotProcessed(const QPixmap& screenshot, const QString& description);
|
||||||
|
public:
|
||||||
|
// 单例访问点
|
||||||
|
static ScreenShotReqDataHandle *getInstance();
|
||||||
|
// 显式销毁
|
||||||
|
static void destroy();
|
||||||
|
|
||||||
|
~ScreenShotReqDataHandle() override;
|
||||||
|
private:
|
||||||
|
QString m_systemInfo;
|
||||||
|
};
|
||||||
@@ -42,15 +42,15 @@ AudioDataHandle::~AudioDataHandle()
|
|||||||
|
|
||||||
void AudioDataHandle::onAudioPacketReceived(const AudioDataTransferObject &packet) {
|
void AudioDataHandle::onAudioPacketReceived(const AudioDataTransferObject &packet) {
|
||||||
// 管理并调用AudioOutput播放流式wav音频
|
// 管理并调用AudioOutput播放流式wav音频
|
||||||
if (packet.isEnd()) { // 如果是结束包
|
if (packet.isEnd()) { // 如果是结束包(空包)
|
||||||
AudioOutput::getInstance()->stopStream(); // 停止播放
|
AudioOutput::getInstance()->stopStream(); // 停止播放
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (packet.isStart()) { // 如果是开始包
|
if (packet.isStart()) { // 如果是开始包(单wav 44字节头)
|
||||||
AudioOutput::getInstance()->startStream(packet.sampleRate(), packet.channelCount(), packet.bitDepth());; // 播放开始
|
AudioOutput::getInstance()->startStream(packet.sampleRate(), packet.channelCount(), packet.bitDepth());; // 播放开始
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 否则播放即可
|
// 否则加入播放队列即可
|
||||||
AudioOutput::getInstance()->pushStreamData(packet.audioData());
|
AudioOutput::getInstance()->pushStreamData(packet.audioData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
//
|
||||||
|
// Created by misaki on 2026/2/1.
|
||||||
|
//
|
||||||
|
#include "ScreenShotReqDataHandle.h"
|
||||||
|
#include "NetWorkDO.h"
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include "ScreenShotDataTransferObject.h"
|
||||||
|
#include "ScreenHelperUtil.hpp"
|
||||||
|
// 初始化静态成员
|
||||||
|
QScopedPointer<ScreenShotReqDataHandle> ScreenShotReqDataHandle::m_instance;
|
||||||
|
QMutex ScreenShotReqDataHandle::m_mutex;
|
||||||
|
|
||||||
|
// 单例实现 (QScopedPointer + Mutex)
|
||||||
|
ScreenShotReqDataHandle* ScreenShotReqDataHandle::getInstance()
|
||||||
|
{
|
||||||
|
if (m_instance.isNull()) {
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
if (m_instance.isNull()) {
|
||||||
|
// 使用 reset 创建实例,因为构造函数是私有的
|
||||||
|
m_instance.reset(new ScreenShotReqDataHandle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m_instance.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenShotReqDataHandle::destroy()
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
if (!m_instance.isNull()) {
|
||||||
|
m_instance.reset(); // 这会触发析构函数
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ScreenShotReqDataHandle::ScreenShotReqDataHandle(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
connect(NetworkDO::getInstance(), &NetworkDO::screenShotPacketReceived,
|
||||||
|
this, &ScreenShotReqDataHandle::onScreenShotPacketReceived);
|
||||||
|
// 初始化时候就构造好关于当前运行平台的信息
|
||||||
|
ScreenHelper::SystemInfo sysInfo = ScreenHelper::getSystemInfo();
|
||||||
|
const QString sysText = QString("System: %1 OS Version: %2 Display Server: %3")
|
||||||
|
.arg(sysInfo.osType, sysInfo.osVersion, sysInfo.displayServer);
|
||||||
|
this->m_systemInfo = sysText;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScreenShotReqDataHandle::~ScreenShotReqDataHandle()
|
||||||
|
{
|
||||||
|
qDebug() << "ScreenShotDataHandle destroyed";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenShotReqDataHandle::onScreenShotPacketReceived(const ScreenShotDataTransferObject &packet) const {
|
||||||
|
qDebug() << "ScreenShot packet request from:" << packet.owner();
|
||||||
|
// 截图当前画面并构造对等DTO发送
|
||||||
|
const ScreenHelper::ScreenshotResult result = ScreenHelper::captureFocusedScreen(); // 获取当前屏幕截图
|
||||||
|
if (!result.success) { // 如果截图失败
|
||||||
|
// TODO: 考虑失败时候构造一个错误DTO给服务端
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ScreenShotDataTransferObject reback; // 构造返回的DTO
|
||||||
|
reback.setData("isSuccess", true).setData("RealTimeScreenShot", result.base64Data)
|
||||||
|
.setData("Width", result.width).setData("Height", result.height)
|
||||||
|
.setData("DescribeInfo", this->m_systemInfo);
|
||||||
|
// 发送DTO
|
||||||
|
NetworkDO::getInstance()->sendPacket(reback);
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
#include "websocketmanager.h"
|
#include "websocketmanager.h"
|
||||||
#include "NetWorkDO.h"
|
#include "NetWorkDO.h"
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include "AudioDataHandle.h"
|
|
||||||
|
|
||||||
NetWorkPage::NetWorkPage(QWidget* parent)
|
NetWorkPage::NetWorkPage(QWidget* parent)
|
||||||
: BasePage(parent)
|
: BasePage(parent)
|
||||||
@@ -73,7 +72,6 @@ void NetWorkPage::initUI() {
|
|||||||
void NetWorkPage::initWebSocketClient() {
|
void NetWorkPage::initWebSocketClient() {
|
||||||
auto* client = WebSocketClient::getInstance(); // 获取单例实例(设置一个默认地址)
|
auto* client = WebSocketClient::getInstance(); // 获取单例实例(设置一个默认地址)
|
||||||
auto* netDO = NetworkDO::getInstance();
|
auto* netDO = NetworkDO::getInstance();
|
||||||
AudioDataHandle::getInstance(); // 初始化音频处理模块
|
|
||||||
// 注入:将底层发送能力赋予 NetworkDO
|
// 注入:将底层发送能力赋予 NetworkDO
|
||||||
netDO->registerSender([client](const QString& type, const QJsonObject& data){
|
netDO->registerSender([client](const QString& type, const QJsonObject& data){
|
||||||
client->sendJson(type, data);
|
client->sendJson(type, data);
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// Created by misaki on 2026/2/1.
|
||||||
|
//
|
||||||
|
/**
|
||||||
|
* 屏幕截图与系统信息获取工具类
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <QString>
|
||||||
|
#include <QSize>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QWindow>
|
||||||
|
#include <QCursor>
|
||||||
|
#include <QSysInfo>
|
||||||
|
class ScreenHelper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// 系统信息struct
|
||||||
|
struct SystemInfo {
|
||||||
|
QString osType; // 例如: "windows", "linux", "macos"
|
||||||
|
QString osVersion; // 例如: "Windows 11 (10.0)", "Ubuntu 22.04"
|
||||||
|
QString displayServer; // 例如: "windows", "cocoa", "xcb" (X11), "wayland"
|
||||||
|
bool isWayland; // 专门标记是否为 Wayland
|
||||||
|
};
|
||||||
|
|
||||||
|
// 截图结果struct
|
||||||
|
struct ScreenshotResult {
|
||||||
|
bool success; // 是否成功
|
||||||
|
QString base64Data; // 图片的Base64字符串 (PNG格式)
|
||||||
|
int width; // 图片宽度
|
||||||
|
int height; // 图片高度
|
||||||
|
QString screenName; // 屏幕名称
|
||||||
|
QString errorMsg; // 如果失败,返回错误信息
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 获取当前焦点屏幕的全屏截图并转换为Base64 \n
|
||||||
|
* 判定逻辑:优先取有焦点的窗口所在屏幕,若无,取鼠标所在屏幕
|
||||||
|
*/
|
||||||
|
static ScreenshotResult captureFocusedScreen();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前操作系统和显示服务信息
|
||||||
|
*/
|
||||||
|
static SystemInfo getSystemInfo();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 私有构造,禁止实例化
|
||||||
|
ScreenHelper() = default;
|
||||||
|
};
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
//
|
||||||
|
// Created by misaki on 2026/2/1.
|
||||||
|
//
|
||||||
|
#include <QDebug>
|
||||||
|
#include "ScreenHelperUtil.hpp"
|
||||||
|
ScreenHelper::ScreenshotResult ScreenHelper::captureFocusedScreen()
|
||||||
|
{
|
||||||
|
ScreenHelper::ScreenshotResult result;
|
||||||
|
result.success = false;
|
||||||
|
// 获取目标屏幕
|
||||||
|
QScreen *targetScreen = nullptr;
|
||||||
|
|
||||||
|
// 首先尝试获取当前应用程序拥有焦点的窗口所在的屏幕
|
||||||
|
QWindow *focusWindow = QGuiApplication::focusWindow();
|
||||||
|
if (focusWindow) {
|
||||||
|
targetScreen = focusWindow->screen();
|
||||||
|
}
|
||||||
|
// 如果没有窗口焦点或者窗口还没显示,获取鼠标光标所在的屏幕
|
||||||
|
if (!targetScreen) {
|
||||||
|
targetScreen = QGuiApplication::screenAt(QCursor::pos());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果以上都失败,回退到主屏幕
|
||||||
|
if (!targetScreen) {
|
||||||
|
targetScreen = QGuiApplication::primaryScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetScreen) {
|
||||||
|
result.errorMsg = "Critical Error: No detectible screen found.";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取屏幕基本信息
|
||||||
|
result.screenName = targetScreen->name();
|
||||||
|
|
||||||
|
// 执行截图
|
||||||
|
// grabWindow(0) 表示截取整个屏幕
|
||||||
|
// 注:在 Wayland 上,这可能需要系统权限或会弹出确认框,或者在某些安全策略下返回黑色图像
|
||||||
|
QPixmap pixmap = targetScreen->grabWindow(0);
|
||||||
|
|
||||||
|
if (pixmap.isNull()) {
|
||||||
|
result.errorMsg = "Failed to grab screen content (Permission denied or System restriction).";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.width = pixmap.width();
|
||||||
|
result.height = pixmap.height();
|
||||||
|
|
||||||
|
// 转换为 Base64
|
||||||
|
QByteArray byteArray;
|
||||||
|
QBuffer buffer(&byteArray);
|
||||||
|
buffer.open(QIODevice::WriteOnly);
|
||||||
|
// 保存为 PNG 格式,质量默认即可
|
||||||
|
if (pixmap.save(&buffer, "PNG")) {
|
||||||
|
result.base64Data = QString::fromLatin1(byteArray.toBase64());
|
||||||
|
result.success = true;
|
||||||
|
} else {
|
||||||
|
result.errorMsg = "Failed to encode image to PNG buffer.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScreenHelper::SystemInfo ScreenHelper::getSystemInfo()
|
||||||
|
{
|
||||||
|
ScreenHelper::SystemInfo info;
|
||||||
|
// 获取操作系统类型
|
||||||
|
info.osType = QSysInfo::productType();
|
||||||
|
|
||||||
|
// 获取详细版本 (例如 Windows 10/11, Ubuntu 20.04)
|
||||||
|
// prettyProductName() 通常能区分 Win10 和 Win11
|
||||||
|
info.osVersion = QSysInfo::prettyProductName();
|
||||||
|
|
||||||
|
// 获取显示服务器类型 (Platform Plugin)
|
||||||
|
// 这里的返回值通常是 QPA 插件的名字
|
||||||
|
// Windows -> "windows"
|
||||||
|
// macOS -> "cocoa"
|
||||||
|
// Linux X11 -> "xcb"
|
||||||
|
// Linux Wayland -> "wayland"
|
||||||
|
QString platformName = QGuiApplication::platformName();
|
||||||
|
info.displayServer = platformName;
|
||||||
|
|
||||||
|
// 专门判断 Wayland
|
||||||
|
info.isWayland = (platformName == "wayland");
|
||||||
|
// 针对 Linux 做更细致的显示名称优化
|
||||||
|
if (platformName == "xcb") {
|
||||||
|
info.displayServer = "X11 (xcb)";
|
||||||
|
} else if (platformName == "wayland") {
|
||||||
|
info.displayServer = "Wayland";
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user