1. 实现单次对话功能,支持语音录制和数据传输
2. 优化静音检测算法,改进RMS平滑处理 3. 添加自动化代理处理,支持点击、拖拽、输入等操作 4. 修复多屏幕环境下鼠标定位问题
This commit is contained in:
Vendored
+8
-4
@@ -29,15 +29,14 @@ if(APPLE)
|
|||||||
find_library(APPLICATIONSERVICES_LIBRARY ApplicationServices REQUIRED)
|
find_library(APPLICATIONSERVICES_LIBRARY ApplicationServices REQUIRED)
|
||||||
target_link_libraries(autogui-cpp PUBLIC ${APPLICATIONSERVICES_LIBRARY})
|
target_link_libraries(autogui-cpp PUBLIC ${APPLICATIONSERVICES_LIBRARY})
|
||||||
|
|
||||||
# Windows平台
|
# Windows平台
|
||||||
elseif(WIN32)
|
elseif(WIN32)
|
||||||
# 链接User32.lib (提供SendInput, GetCursorPos等API)
|
# 链接User32.lib (提供SendInput, GetCursorPos等API)
|
||||||
target_link_libraries(autogui-cpp PUBLIC User32)
|
target_link_libraries(autogui-cpp PUBLIC User32)
|
||||||
|
|
||||||
# Linux/Unix平台
|
# Linux/Unix平台
|
||||||
elseif(UNIX AND NOT APPLE)
|
elseif(UNIX AND NOT APPLE)
|
||||||
# Linux需要X11后端(Wayland尚未支持)
|
# Linux需要X11后端(Wayland尚未支持)
|
||||||
# Linux需要X11后端
|
|
||||||
find_package(X11 REQUIRED)
|
find_package(X11 REQUIRED)
|
||||||
# 查找Xtst库(XTest扩展)
|
# 查找Xtst库(XTest扩展)
|
||||||
find_library(X11_Xtst_LIB Xtst)
|
find_library(X11_Xtst_LIB Xtst)
|
||||||
@@ -49,15 +48,20 @@ elseif(UNIX AND NOT APPLE)
|
|||||||
if(NOT X11_Xext_LIB)
|
if(NOT X11_Xext_LIB)
|
||||||
message(FATAL_ERROR "Xext library not found.")
|
message(FATAL_ERROR "Xext library not found.")
|
||||||
endif()
|
endif()
|
||||||
|
# 查找XRandR库(Xrandr扩展)
|
||||||
|
find_library(X11_Xrandr_LIB Xrandr)
|
||||||
|
if(NOT X11_Xrandr_LIB)
|
||||||
|
message(FATAL_ERROR "XRandR library not found. Install libxrandr-dev.")
|
||||||
|
endif()
|
||||||
# 链接所有必需的X11库
|
# 链接所有必需的X11库
|
||||||
target_link_libraries(autogui-cpp PUBLIC
|
target_link_libraries(autogui-cpp PUBLIC
|
||||||
${X11_LIBRARIES}
|
${X11_LIBRARIES}
|
||||||
${X11_Xtst_LIB}
|
${X11_Xtst_LIB}
|
||||||
${X11_Xext_LIB}
|
${X11_Xext_LIB}
|
||||||
|
${X11_Xrandr_LIB}
|
||||||
)
|
)
|
||||||
target_include_directories(autogui-cpp PUBLIC ${X11_INCLUDE_DIR})
|
target_include_directories(autogui-cpp PUBLIC ${X11_INCLUDE_DIR})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# 集成方式: add_subdirectory
|
# 集成方式: add_subdirectory
|
||||||
# 在你的项目CMakeLists.txt中:
|
# 在你的项目CMakeLists.txt中:
|
||||||
# add_subdirectory(autogui-cpp)
|
# add_subdirectory(autogui-cpp)
|
||||||
|
|||||||
Vendored
+193
-14
@@ -10,11 +10,10 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <cstring>
|
||||||
#ifndef M_PI
|
#ifndef M_PI
|
||||||
# define M_PI 3.14159265358979323846
|
# define M_PI 3.14159265358979323846
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace AutoGUI {
|
namespace AutoGUI {
|
||||||
|
|
||||||
// 内部辅助函数
|
// 内部辅助函数
|
||||||
@@ -33,6 +32,139 @@ Robot::Point getCurrentPosition() { return Robot::Mouse::GetPosition(); }
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/extensions/Xrandr.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
// 获取所有屏幕信息
|
||||||
|
std::vector<ScreenInfo> getAllScreens() {
|
||||||
|
std::vector<ScreenInfo> screens;
|
||||||
|
Display* display = XOpenDisplay(nullptr);
|
||||||
|
if (!display) return screens;
|
||||||
|
|
||||||
|
XRRScreenResources* resources = XRRGetScreenResources(display, DefaultRootWindow(display));
|
||||||
|
if (resources) {
|
||||||
|
for (int i = 0; i < resources->noutput; i++) {
|
||||||
|
XRROutputInfo* output = XRRGetOutputInfo(display, resources, resources->outputs[i]);
|
||||||
|
if (output && output->connection == RR_Connected && output->crtc) {
|
||||||
|
XRRCrtcInfo* crtc = XRRGetCrtcInfo(display, resources, output->crtc);
|
||||||
|
if (crtc) {
|
||||||
|
ScreenInfo info;
|
||||||
|
info.id = i;
|
||||||
|
info.x = crtc->x;
|
||||||
|
info.y = crtc->y;
|
||||||
|
info.width = crtc->width;
|
||||||
|
info.height = crtc->height;
|
||||||
|
info.isPrimary = (output->name && strcmp(output->name, "primary") == 0);
|
||||||
|
// 或者使用 XRRGetOutputPrimary 来判断
|
||||||
|
screens.push_back(info);
|
||||||
|
XRRFreeCrtcInfo(crtc);
|
||||||
|
}
|
||||||
|
XRRFreeOutputInfo(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
XRRFreeScreenResources(resources);
|
||||||
|
}
|
||||||
|
XCloseDisplay(display);
|
||||||
|
return screens;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前鼠标所在的屏幕
|
||||||
|
ScreenInfo getCurrentScreen() {
|
||||||
|
auto screens = getAllScreens();
|
||||||
|
if (screens.empty()) return {0, 0, 0, 1920, 1080, true};
|
||||||
|
|
||||||
|
// 获取当前鼠标位置(虚拟桌面绝对坐标)
|
||||||
|
Robot::Point mouse = position();
|
||||||
|
|
||||||
|
// 找到包含该点的屏幕
|
||||||
|
for (const auto& screen : screens) {
|
||||||
|
if (mouse.x >= screen.x && mouse.x < screen.x + screen.width &&
|
||||||
|
mouse.y >= screen.y && mouse.y < screen.y + screen.height) {
|
||||||
|
return screen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认返回主屏或第一个屏幕
|
||||||
|
auto it = std::find_if(screens.begin(), screens.end(),
|
||||||
|
[](const ScreenInfo& s) { return s.isPrimary; });
|
||||||
|
return (it != screens.end()) ? *it : screens[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 相对于当前屏幕移动
|
||||||
|
void moveToOnCurrentScreen(int x, int y, double duration) {
|
||||||
|
const ScreenInfo current = getCurrentScreen();
|
||||||
|
// 转换为虚拟桌面绝对坐标
|
||||||
|
int absX = current.x + x;
|
||||||
|
int absY = current.y + y;
|
||||||
|
moveTo(absX, absY, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前屏幕尺寸
|
||||||
|
Robot::Point getScreenSize(int screenId) {
|
||||||
|
if (screenId == -1) {
|
||||||
|
ScreenInfo current = getCurrentScreen();
|
||||||
|
return {current.width, current.height};
|
||||||
|
}
|
||||||
|
auto screens = getAllScreens();
|
||||||
|
for (const auto& s : screens) {
|
||||||
|
if (s.id == screenId) return {s.width, s.height};
|
||||||
|
}
|
||||||
|
return {1920, 1080};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
std::vector<ScreenInfo> getAllScreens() {
|
||||||
|
std::vector<ScreenInfo> screens;
|
||||||
|
EnumDisplayMonitors(nullptr, nullptr,
|
||||||
|
[](HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) -> BOOL {
|
||||||
|
auto* screens = reinterpret_cast<std::vector<ScreenInfo>*>(dwData);
|
||||||
|
MONITORINFOEX info;
|
||||||
|
info.cbSize = sizeof(info);
|
||||||
|
if (GetMonitorInfo(hMonitor, &info)) {
|
||||||
|
ScreenInfo si;
|
||||||
|
si.id = screens->size();
|
||||||
|
si.x = info.rcMonitor.left;
|
||||||
|
si.y = info.rcMonitor.top;
|
||||||
|
si.width = info.rcMonitor.right - info.rcMonitor.left;
|
||||||
|
si.height = info.rcMonitor.bottom - info.rcMonitor.top;
|
||||||
|
si.isPrimary = (info.dwFlags & MONITORINFOF_PRIMARY) != 0;
|
||||||
|
screens->push_back(si);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}, reinterpret_cast<LPARAM>(&screens));
|
||||||
|
return screens;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScreenInfo getCurrentScreen() {
|
||||||
|
// 获取当前鼠标位置
|
||||||
|
POINT pt;
|
||||||
|
GetCursorPos(&pt);
|
||||||
|
// 查找包含该点的显示器
|
||||||
|
HMONITOR hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
|
||||||
|
MONITORINFOEX info;
|
||||||
|
info.cbSize = sizeof(info);
|
||||||
|
GetMonitorInfo(hMonitor, &info);
|
||||||
|
|
||||||
|
ScreenInfo si;
|
||||||
|
si.x = info.rcMonitor.left;
|
||||||
|
si.y = info.rcMonitor.top;
|
||||||
|
si.width = info.rcMonitor.right - info.rcMonitor.left;
|
||||||
|
si.height = info.rcMonitor.bottom - info.rcMonitor.top;
|
||||||
|
return si;
|
||||||
|
}
|
||||||
|
|
||||||
|
void moveToOnCurrentScreen(int x, int y, double duration) {
|
||||||
|
ScreenInfo current = getCurrentScreen();
|
||||||
|
moveTo(current.x + x, current.y + y, duration);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// 实现主要API函数
|
// 实现主要API函数
|
||||||
void moveTo(int x, int y, double duration) {
|
void moveTo(int x, int y, double duration) {
|
||||||
Robot::Point target{x, y};
|
Robot::Point target{x, y};
|
||||||
@@ -269,37 +401,84 @@ void sleep(double seconds) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#include <CoreGraphics/CoreGraphics.h>
|
||||||
|
|
||||||
|
#elif defined(__linux__)
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/extensions/Xrandr.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
// 平台特定函数实现
|
// 平台特定函数实现
|
||||||
Robot::Point size() {
|
Robot::Point size() {
|
||||||
// 平台特定实现
|
// 平台特定实现
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// Windows实现
|
// Windows实现
|
||||||
#include <Windows.h>
|
|
||||||
int width = GetSystemMetrics(SM_CXSCREEN);
|
int width = GetSystemMetrics(SM_CXSCREEN);
|
||||||
int height = GetSystemMetrics(SM_CYSCREEN);
|
int height = GetSystemMetrics(SM_CYSCREEN);
|
||||||
return {width, height};
|
return {width, height};
|
||||||
|
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
// macOS实现
|
// macOS实现
|
||||||
#include <CoreGraphics/CoreGraphics.h>
|
|
||||||
CGRect mainDisplayBounds = CGDisplayBounds(CGMainDisplayID());
|
CGRect mainDisplayBounds = CGDisplayBounds(CGMainDisplayID());
|
||||||
int width = static_cast<int>(CGRectGetWidth(mainDisplayBounds));
|
int width = static_cast<int>(CGRectGetWidth(mainDisplayBounds));
|
||||||
int height = static_cast<int>(CGRectGetHeight(mainDisplayBounds));
|
int height = static_cast<int>(CGRectGetHeight(mainDisplayBounds));
|
||||||
return {width, height};
|
return {width, height};
|
||||||
|
|
||||||
#elif defined(__linux__)
|
#elif defined(__linux__)
|
||||||
// Linux实现(X11)
|
|
||||||
#include <X11/Xlib.h>
|
|
||||||
|
|
||||||
Display *display = XOpenDisplay(nullptr);
|
Display *display = XOpenDisplay(nullptr);
|
||||||
if (display) {
|
if (!display) return {1920, 1080};
|
||||||
const Screen *screen = DefaultScreenOfDisplay(display);
|
|
||||||
const int width = WidthOfScreen(screen);
|
int width = 0, height = 0;
|
||||||
const int height = HeightOfScreen(screen);
|
|
||||||
XCloseDisplay(display);
|
// 使用 XRRGetScreenResources (兼容 XRandR 1.2+,比 Current 版本兼容性更好)
|
||||||
return {width, height};
|
XRRScreenResources *resources = XRRGetScreenResources(display, DefaultRootWindow(display));
|
||||||
|
if (resources) {
|
||||||
|
RROutput target_output = None;
|
||||||
|
|
||||||
|
// 条件编译:只在 XRandR 1.3+ 时使用 Primary Output 功能
|
||||||
|
#if defined(RANDR_MAJOR) && defined(RANDR_MINOR)
|
||||||
|
#if RANDR_MAJOR > 1 || (RANDR_MAJOR == 1 && RANDR_MINOR >= 3)
|
||||||
|
target_output = XRRGetOutputPrimary(display, DefaultRootWindow(display));
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 如果没有获取到主显示器(或版本太低),使用第一个已连接的显示器
|
||||||
|
if (target_output == None) {
|
||||||
|
for (int i = 0; i < resources->noutput; i++) {
|
||||||
|
XRROutputInfo *info = XRRGetOutputInfo(display, resources, resources->outputs[i]);
|
||||||
|
if (info) {
|
||||||
|
if (info->connection == 0) {
|
||||||
|
target_output = resources->outputs[i];
|
||||||
|
XRRFreeOutputInfo(info);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return {1920, 1080}; // 默认值
|
XRRFreeOutputInfo(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取选中显示器的分辨率
|
||||||
|
if (target_output != None) {
|
||||||
|
XRROutputInfo *output_info = XRRGetOutputInfo(display, resources, target_output);
|
||||||
|
if (output_info && output_info->crtc) {
|
||||||
|
XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(display, resources, output_info->crtc);
|
||||||
|
if (crtc_info) {
|
||||||
|
width = static_cast<int>(crtc_info->width);
|
||||||
|
height = static_cast<int>(crtc_info->height);
|
||||||
|
XRRFreeCrtcInfo(crtc_info);
|
||||||
|
}
|
||||||
|
XRRFreeOutputInfo(output_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
XRRFreeScreenResources(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
XCloseDisplay(display);
|
||||||
|
return (width > 0 && height > 0) ? Robot::Point{width, height} : Robot::Point{1920, 1080};
|
||||||
|
|
||||||
#else
|
#else
|
||||||
// 其他平台
|
// 其他平台
|
||||||
|
|||||||
Vendored
+21
@@ -125,6 +125,27 @@ inline bool isSpecialKey(const std::string& key) {
|
|||||||
return std::find(specialKeys.begin(), specialKeys.end(), lower) != specialKeys.end();
|
return std::find(specialKeys.begin(), specialKeys.end(), lower) != specialKeys.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解决多屏幕移动鼠标问题
|
||||||
|
struct ScreenInfo {
|
||||||
|
int id; // 屏幕ID
|
||||||
|
int x, y; // 相对于虚拟桌面原点的偏移
|
||||||
|
int width, height;// 屏幕尺寸
|
||||||
|
bool isPrimary; // 是否主屏
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取所有屏幕信息
|
||||||
|
std::vector<ScreenInfo> getAllScreens();
|
||||||
|
|
||||||
|
// 获取当前鼠标所在的屏幕
|
||||||
|
ScreenInfo getCurrentScreen();
|
||||||
|
|
||||||
|
// 相对于当前屏幕移动鼠标(以当前屏幕左上角为原点)
|
||||||
|
void moveToOnCurrentScreen(int x, int y, double duration = 0.0);
|
||||||
|
|
||||||
|
// 获取指定屏幕的尺寸(替代原来的size(),支持多屏)
|
||||||
|
Robot::Point getScreenSize(int screenId = -1); // -1表示当前屏幕
|
||||||
|
|
||||||
|
|
||||||
// 主要 API 函数
|
// 主要 API 函数
|
||||||
/**
|
/**
|
||||||
* @brief 移动鼠标到指定位置
|
* @brief 移动鼠标到指定位置
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ make
|
|||||||
```
|
```
|
||||||
|
|
||||||
注意:本项目只是Yosuga的客户端部分,完整的还包括服务端
|
注意:本项目只是Yosuga的客户端部分,完整的还包括服务端
|
||||||
- 服务端项目地址见:
|
- 服务端项目地址见:https://github.com/Misakityan/Yosuga_server
|
||||||
|
|
||||||
当前支持平台(已测试过的):
|
当前支持平台(已测试过的):
|
||||||
- Windows: Windows 10
|
- Windows: Windows 10
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ private:
|
|||||||
static QMutex m_mutex;
|
static QMutex m_mutex;
|
||||||
private slots:
|
private slots:
|
||||||
// 业务接收槽函数
|
// 业务接收槽函数
|
||||||
|
void onRecordingFinished_Byte(const QByteArray &wavData);
|
||||||
public:
|
public:
|
||||||
// 单例访问点
|
// 单例访问点
|
||||||
static AppCore *getInstance();
|
static AppCore *getInstance();
|
||||||
@@ -36,4 +36,7 @@ public:
|
|||||||
|
|
||||||
~AppCore() override;
|
~AppCore() override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// 单次对话
|
||||||
|
void SingleExchange();
|
||||||
};
|
};
|
||||||
@@ -9,6 +9,9 @@
|
|||||||
#include "AutoAgentHandle.h"
|
#include "AutoAgentHandle.h"
|
||||||
#include "ScreenShotReqDataHandle.h"
|
#include "ScreenShotReqDataHandle.h"
|
||||||
|
|
||||||
|
#include "AudioInput.h"
|
||||||
|
#include "NetWorkDO.h"
|
||||||
|
#include "websocketmanager.h"
|
||||||
// 初始化静态成员
|
// 初始化静态成员
|
||||||
QScopedPointer<AppCore> AppCore::m_instance;
|
QScopedPointer<AppCore> AppCore::m_instance;
|
||||||
QMutex AppCore::m_mutex;
|
QMutex AppCore::m_mutex;
|
||||||
@@ -40,6 +43,15 @@ AppCore::AppCore(QObject *parent) : QObject(parent)
|
|||||||
AudioDataHandle::getInstance();
|
AudioDataHandle::getInstance();
|
||||||
AutoAgentHandle::getInstance();
|
AutoAgentHandle::getInstance();
|
||||||
ScreenShotReqDataHandle::getInstance();
|
ScreenShotReqDataHandle::getInstance();
|
||||||
|
// 注入发送接口
|
||||||
|
NetworkDO::getInstance()->registerSender([](const QString& type, const QJsonObject& data){
|
||||||
|
WebSocketClient::getInstance()->sendJson(type, data);
|
||||||
|
});
|
||||||
|
// TODO Test
|
||||||
|
AudioInput::getInstance()->setAudioPath(QDir::currentPath(), "/temp.wav");
|
||||||
|
// 连接必要的信号
|
||||||
|
connect(AudioInput::getInstance(), &AudioInput::recordingFinished_Byte,
|
||||||
|
this, &AppCore::onRecordingFinished_Byte); // 录音完成信号
|
||||||
}
|
}
|
||||||
|
|
||||||
AppCore::~AppCore()
|
AppCore::~AppCore()
|
||||||
@@ -52,3 +64,16 @@ AppCore::~AppCore()
|
|||||||
|
|
||||||
qDebug() << "AppCore destroyed";
|
qDebug() << "AppCore destroyed";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AppCore::SingleExchange() {
|
||||||
|
// 开始录音,录音结束后会触发录音完成信号
|
||||||
|
AudioInput::getInstance()->startAutoStopAudio(AudioInput::getInstance()->getSilenceThreshold(), 800);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppCore::onRecordingFinished_Byte(const QByteArray &wavData) {
|
||||||
|
// 将录音数据发送给服务端
|
||||||
|
AudioDataTransferObject packet;
|
||||||
|
packet.setData("isStream", false).setData("data", wavData.toBase64().data());
|
||||||
|
NetworkDO::getInstance()->sendPacket(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public:
|
|||||||
// 静态工厂方法
|
// 静态工厂方法
|
||||||
static ScreenShotDataTransferObject fromJson(const QJsonObject& json);
|
static ScreenShotDataTransferObject fromJson(const QJsonObject& json);
|
||||||
|
|
||||||
[[nodiscard]] QString type() const override { return "screenshot_req"; }
|
[[nodiscard]] QString type() const override { return "screenshot_data"; }
|
||||||
|
|
||||||
// 序列化
|
// 序列化
|
||||||
[[nodiscard]] QJsonObject toJson() const override; // 通过多态即可统一调用方式
|
[[nodiscard]] QJsonObject toJson() const override; // 通过多态即可统一调用方式
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ void NetworkDO::onDataReceived(const QString& type, const QJsonObject& 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") {
|
else if (type == "screenshot_data") {
|
||||||
emit screenShotPacketReceived(ScreenShotDataTransferObject::fromJson(data));
|
emit screenShotPacketReceived(ScreenShotDataTransferObject::fromJson(data));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ ScreenShotDataTransferObject ScreenShotDataTransferObject::fromJson(const QJsonO
|
|||||||
|
|
||||||
// 调用构造函数创建对象
|
// 调用构造函数创建对象
|
||||||
return ScreenShotDataTransferObject(owner, isSuccess, realtimeScreenShot,
|
return ScreenShotDataTransferObject(owner, isSuccess, realtimeScreenShot,
|
||||||
width, height, describeInfo);
|
width, height, describeInfo, LLMResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 序列化为 JSON
|
// 序列化为 JSON
|
||||||
|
|||||||
@@ -148,5 +148,7 @@ private:
|
|||||||
qreal m_silenceThreshold = 1200; /// 静音阈值
|
qreal m_silenceThreshold = 1200; /// 静音阈值
|
||||||
int m_silenceDuration = 1500; /// 静音持续时间
|
int m_silenceDuration = 1500; /// 静音持续时间
|
||||||
qreal m_smoothRms = 0.0; /// 平滑RMS值(用于防止低频杂波突然打断静音检测)
|
qreal m_smoothRms = 0.0; /// 平滑RMS值(用于防止低频杂波突然打断静音检测)
|
||||||
|
|
||||||
|
bool m_hasVoiceDetected = false; /// 是否已检测到人声
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ void AudioInput::onReadyRead()
|
|||||||
const qreal currentRms = calculateRMS(data);
|
const qreal currentRms = calculateRMS(data);
|
||||||
m_rmsValue = currentRms;
|
m_rmsValue = currentRms;
|
||||||
// 计算平滑RMS (用于防止低频杂波突然打断静音检测)
|
// 计算平滑RMS (用于防止低频杂波突然打断静音检测)
|
||||||
constexpr qreal alpha = 0.3; // 70% 历史权重, 30% 当前权重
|
constexpr qreal alpha = 0.15; // 85% 历史权重, 15% 当前权重
|
||||||
if (qFuzzyIsNull(m_smoothRms)) {
|
if (qFuzzyIsNull(m_smoothRms)) {
|
||||||
// 如果是第一帧数据,直接赋值,避免从0开始慢慢爬升
|
// 如果是第一帧数据,直接赋值,避免从0开始慢慢爬升
|
||||||
m_smoothRms = currentRms;
|
m_smoothRms = currentRms;
|
||||||
@@ -163,13 +163,30 @@ void AudioInput::onReadyRead()
|
|||||||
qDebug() << "Raw:" << currentRms << " Smooth:" << m_smoothRms;
|
qDebug() << "Raw:" << currentRms << " Smooth:" << m_smoothRms;
|
||||||
|
|
||||||
if (m_smoothRms < m_silenceThreshold) {
|
if (m_smoothRms < m_silenceThreshold) {
|
||||||
// 静音状态
|
// [当前是静音]
|
||||||
|
|
||||||
|
// 如果之前已经检测到过人声(说明是话说完了,或者是句间停顿)
|
||||||
|
if (m_hasVoiceDetected) {
|
||||||
|
// 启动/保持“短时”静音检测 (由 AppCore 传入,例如 500ms 或 1500ms)
|
||||||
if (!m_silenceTimer->isActive()) {
|
if (!m_silenceTimer->isActive()) {
|
||||||
m_silenceTimer->start(m_silenceDuration);
|
m_silenceTimer->start(m_silenceDuration);
|
||||||
}
|
}
|
||||||
|
// 如果 Timer 正在运行,就让它继续倒计时,超时会自动触发 stopAudio
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// [还没有检测到过人声] (起始静音)
|
||||||
|
// 这里不需要做额外操作,startAutoStopAudio 里设置的 5000ms 长定时器在跑
|
||||||
|
// 允许用户深呼吸或准备
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 有声音,重置定时器
|
// [当前有声音]
|
||||||
|
m_hasVoiceDetected = true; // 标记:已经有人说话了
|
||||||
|
|
||||||
|
// 重置静音定时器
|
||||||
|
// 只要有人说话,就不断重置定时器,防止断录
|
||||||
m_silenceTimer->stop();
|
m_silenceTimer->stop();
|
||||||
|
// 这里可以预设启动,也可以不启动,只要有声音就会一直 stop
|
||||||
|
// 为了安全,设为 silenceDuration
|
||||||
m_silenceTimer->start(m_silenceDuration);
|
m_silenceTimer->start(m_silenceDuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,11 +263,20 @@ void AudioInput::startAutoStopAudio(const qreal silenceThreshold, const int sile
|
|||||||
m_silenceThreshold = silenceThreshold;
|
m_silenceThreshold = silenceThreshold;
|
||||||
m_silenceDuration = silenceDuration;
|
m_silenceDuration = silenceDuration;
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
m_hasVoiceDetected = false;
|
||||||
|
m_smoothRms = 0.0;
|
||||||
startAudio();
|
startAudio();
|
||||||
|
|
||||||
|
// 延迟200ms是为了避开硬件启动时的爆音,但不需要立即启动短时倒计时
|
||||||
// 延迟启动静音检测,给一点缓冲时间
|
// 延迟启动静音检测,给一点缓冲时间
|
||||||
QTimer::singleShot(200, this, [this](){
|
QTimer::singleShot(200, this, [this](){
|
||||||
m_silenceTimer->start(m_silenceDuration);
|
if(isAutoRecording) { // 确保还在录音状态
|
||||||
|
// 如果还没检测到声音,给5秒的等待时间;如果检测到了,逻辑由onReadyRead接管
|
||||||
|
if(!m_hasVoiceDetected) {
|
||||||
|
m_silenceTimer->start(5000); // 5秒没声音就停止
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,8 +311,12 @@ void AudioInput::thresholdTimeout()
|
|||||||
// 防止浮点误差导致负数
|
// 防止浮点误差导致负数
|
||||||
if (variance < 0) variance = 0;
|
if (variance < 0) variance = 0;
|
||||||
const double stdDev = std::sqrt(variance);
|
const double stdDev = std::sqrt(variance);
|
||||||
|
|
||||||
|
// 增加一个固定的偏移量 offset
|
||||||
|
// 确保即使环境有轻微波动,也不会触发录音
|
||||||
|
constexpr double offset = 80.0;
|
||||||
// 阈值 = 均值 + 2 * 标准差
|
// 阈值 = 均值 + 2 * 标准差
|
||||||
const double bestThreshold = mean + 3 * stdDev;
|
const double bestThreshold = mean + (3 * stdDev) + offset;
|
||||||
m_silenceThreshold = std::max(bestThreshold, 150.0);
|
m_silenceThreshold = std::max(bestThreshold, 150.0);
|
||||||
m_silenceThreshold = std::min(m_silenceThreshold, 30000.0);
|
m_silenceThreshold = std::min(m_silenceThreshold, 30000.0);
|
||||||
qDebug() << "Auto Threshold Calc -> Mean:" << mean
|
qDebug() << "Auto Threshold Calc -> Mean:" << mean
|
||||||
|
|||||||
@@ -42,24 +42,40 @@ AutoAgentHandle::~AutoAgentHandle()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AutoAgentHandle::onAutoAgentPacketReceived(const AutoAgentDataObject &packet) {
|
void AutoAgentHandle::onAutoAgentPacketReceived(const AutoAgentDataObject &packet) {
|
||||||
|
qDebug() << "Received AutoAgent packet: " << packet.getAction();
|
||||||
if (packet.getAction() == "click") { // 单击
|
if (packet.getAction() == "click") { // 单击
|
||||||
qDebug() << "Click: " << packet.getX1() << ", " << packet.getY1();
|
qDebug() << "Click: " << packet.getX1() << ", " << packet.getY1();
|
||||||
AutoGUI::moveTo(packet.getX1(), packet.getY1(), 0.6);
|
AutoGUI::moveToOnCurrentScreen(packet.getX1(), packet.getY1(), 0.6);
|
||||||
AutoGUI::click(packet.getX1(), packet.getY1());
|
AutoGUI::click(packet.getX1(), packet.getY1());
|
||||||
}
|
}
|
||||||
if (packet.getAction() == "left_double") { // 双击
|
if (packet.getAction() == "left_double") { // 双击
|
||||||
qDebug() << "Double click: " << packet.getX1() << ", " << packet.getY1();
|
qDebug() << "Double click: " << packet.getX1() << ", " << packet.getY1();
|
||||||
AutoGUI::moveTo(packet.getX1(), packet.getY1(), 0.6);
|
AutoGUI::moveToOnCurrentScreen(packet.getX1(), packet.getY1(), 0.6);
|
||||||
AutoGUI::leftDouble(packet.getX1(), packet.getY1());
|
AutoGUI::leftDouble(packet.getX1(), packet.getY1());
|
||||||
}
|
}
|
||||||
if (packet.getAction() == "right_single") { // 右键单击
|
if (packet.getAction() == "right_single") { // 右键单击
|
||||||
qDebug() << "Right click: " << packet.getX1() << ", " << packet.getY1();
|
qDebug() << "Right click: " << packet.getX1() << ", " << packet.getY1();
|
||||||
AutoGUI::moveTo(packet.getX1(), packet.getY1(), 0.6);
|
AutoGUI::moveToOnCurrentScreen(packet.getX1(), packet.getY1(), 0.6);
|
||||||
AutoGUI::rightSingle(packet.getX1(), packet.getY1());
|
AutoGUI::rightSingle(packet.getX1(), packet.getY1());
|
||||||
}
|
}
|
||||||
if (packet.getAction() == "drag") { // 拖拽
|
if (packet.getAction() == "drag") { // 拖拽
|
||||||
qDebug() << "Drag: " << packet.getX1() << ", " << packet.getY1() << " to " << packet.getX2() << ", " << packet.getY2();
|
qDebug() << "Drag: " << packet.getX1() << ", " << packet.getY1() << " to " << packet.getX2() << ", " << packet.getY2();
|
||||||
AutoGUI::drag(packet.getX1(), packet.getY1(), packet.getX2(), packet.getY2(), 0.8);
|
AutoGUI::drag(packet.getX1(), packet.getY1(), packet.getX2(), packet.getY2(), 1.2);
|
||||||
}
|
}
|
||||||
// TODO: 快捷键,输入文本,滚动待实现
|
// TODO: 快捷键,输入文本,滚动待实现
|
||||||
|
if (packet.getAction() == "type") { // 输入文本
|
||||||
|
qDebug() << "Type: " << packet.getContent();
|
||||||
|
AutoGUI::type(packet.getContent().toStdString(), 0.08);
|
||||||
|
}
|
||||||
|
if (packet.getAction() == "scroll") { // 滚动
|
||||||
|
qDebug() << "Scroll: " << packet.getX1() << ", " << packet.getY1() << packet.getDirection();
|
||||||
|
if (packet.getDirection() == "up") {
|
||||||
|
AutoGUI::moveToOnCurrentScreen(packet.getX1(), packet.getY1(), 0.1);
|
||||||
|
AutoGUI::scroll(40, 1);
|
||||||
|
}
|
||||||
|
if (packet.getDirection() == "down") {
|
||||||
|
AutoGUI::moveToOnCurrentScreen(packet.getX1(), packet.getY1(), 0.1);
|
||||||
|
AutoGUI::scroll(40, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -42,6 +42,7 @@ ScreenShotReqDataHandle::ScreenShotReqDataHandle(QObject *parent) : QObject(pare
|
|||||||
const QString sysText = QString("System: %1 OS Version: %2 Display Server: %3")
|
const QString sysText = QString("System: %1 OS Version: %2 Display Server: %3")
|
||||||
.arg(sysInfo.osType, sysInfo.osVersion, sysInfo.displayServer);
|
.arg(sysInfo.osType, sysInfo.osVersion, sysInfo.displayServer);
|
||||||
this->m_systemInfo = sysText;
|
this->m_systemInfo = sysText;
|
||||||
|
qDebug() << "当前平台信息为: " << sysText;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScreenShotReqDataHandle::~ScreenShotReqDataHandle()
|
ScreenShotReqDataHandle::~ScreenShotReqDataHandle()
|
||||||
@@ -55,12 +56,14 @@ void ScreenShotReqDataHandle::onScreenShotPacketReceived(const ScreenShotDataTra
|
|||||||
const ScreenHelper::ScreenshotResult result = ScreenHelper::captureFocusedScreen(); // 获取当前屏幕截图
|
const ScreenHelper::ScreenshotResult result = ScreenHelper::captureFocusedScreen(); // 获取当前屏幕截图
|
||||||
if (!result.success) { // 如果截图失败
|
if (!result.success) { // 如果截图失败
|
||||||
// TODO: 考虑失败时候构造一个错误DTO给服务端
|
// TODO: 考虑失败时候构造一个错误DTO给服务端
|
||||||
|
qDebug() << "截图失败: " << result.errorMsg;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ScreenShotDataTransferObject reback; // 构造返回的DTO
|
ScreenShotDataTransferObject reback; // 构造返回的DTO
|
||||||
reback.setData("isSuccess", true).setData("RealTimeScreenShot", result.base64Data)
|
reback.setData("isSuccess", true).setData("RealTimeScreenShot", result.base64Data)
|
||||||
.setData("Width", result.width).setData("Height", result.height)
|
.setData("Width", result.width).setData("Height", result.height)
|
||||||
.setData("DescribeInfo", this->m_systemInfo);
|
.setData("DescribeInfo", this->m_systemInfo).setData("LLMResponse", packet.LLMResponse());
|
||||||
// 发送DTO
|
// 发送DTO
|
||||||
NetworkDO::getInstance()->sendPacket(reback);
|
NetworkDO::getInstance()->sendPacket(reback);
|
||||||
|
qDebug() << "ScreenShot packet sent to:" << packet.owner();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,23 +7,22 @@
|
|||||||
* 基于Ela UI的菜单控件
|
* 基于Ela UI的菜单控件
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ElaMenu.h"
|
#include <ElaMenu.h>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QPoint>
|
#include <QPoint>
|
||||||
#include <QScopedPointer> // 智能指针
|
#include <QScopedPointer> // 智能指针
|
||||||
|
|
||||||
#include "Setting.h"
|
#include "Setting.h"
|
||||||
#include "networkmanager.h"
|
#include "networkmanager.h"
|
||||||
#include "socketmanager.h"
|
#include "socketmanager.h"
|
||||||
|
|
||||||
class Menu : public ElaMenu
|
class Menu final : public ElaMenu
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Menu(QWidget *parent = nullptr);
|
explicit Menu(QWidget *parent = nullptr);
|
||||||
~Menu();
|
~Menu() override;
|
||||||
void showMenu(const QPoint &pos);
|
void showMenu(const QPoint &pos);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@@ -34,7 +33,8 @@ signals:
|
|||||||
private:
|
private:
|
||||||
void createMenu();
|
void createMenu();
|
||||||
QAction *toggleThe; /// 切换主题(全局)
|
QAction *toggleThe; /// 切换主题(全局)
|
||||||
QAction *startExchangeAction; /// 开启对话
|
QAction *startSingleExchangeAction; /// 开启单次对话
|
||||||
|
QAction *startContinueExchangeAction; /// 开启连续对话
|
||||||
QAction *settingsAction; /// 设置
|
QAction *settingsAction; /// 设置
|
||||||
QAction *closeAction; /// 关闭
|
QAction *closeAction; /// 关闭
|
||||||
|
|
||||||
|
|||||||
+15
-11
@@ -5,29 +5,29 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "TextRenderer.h"
|
#include "TextRenderer.h"
|
||||||
// #include "AudioInput.h"
|
#include "AppCore.h"
|
||||||
// #include "AudioOutput.h"
|
|
||||||
|
|
||||||
Menu::Menu(QWidget *parent)
|
Menu::Menu(QWidget *parent)
|
||||||
: ElaMenu(parent)
|
: ElaMenu(parent)
|
||||||
{
|
{
|
||||||
// 设置默认主题
|
// 设置默认主题
|
||||||
eTheme->setThemeMode(ElaThemeType::Dark);
|
eTheme->setThemeMode(ElaThemeType::Dark);
|
||||||
|
|
||||||
createMenu();
|
createMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu::~Menu()
|
Menu::~Menu() {
|
||||||
{
|
AppCore::destroy(); // 显式销毁 AppCore
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::createMenu()
|
void Menu::createMenu()
|
||||||
{
|
{
|
||||||
toggleThe = addAction("切换主题");
|
toggleThe = addAction("切换主题");
|
||||||
|
|
||||||
|
// 单次对话功能按钮
|
||||||
|
startSingleExchangeAction = addAction("单次对话(测试)");
|
||||||
|
|
||||||
// 连续对话功能按钮
|
// 连续对话功能按钮
|
||||||
startExchangeAction = addAction("连续对话(测试)");
|
startContinueExchangeAction = addAction("连续对话(测试)");
|
||||||
|
|
||||||
// 添加设置按钮
|
// 添加设置按钮
|
||||||
settingsAction = addAction("设置");
|
settingsAction = addAction("设置");
|
||||||
@@ -42,10 +42,14 @@ void Menu::createMenu()
|
|||||||
toggleTheme();
|
toggleTheme();
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO 连续对话功能,需要优化实现
|
// 单次对话功能
|
||||||
connect(startExchangeAction, &QAction::triggered, this, [this]() {
|
connect(startSingleExchangeAction, &QAction::triggered, this, []() {
|
||||||
// startExchange();
|
qDebug() << "Start SingleExchange triggered";
|
||||||
qDebug() << "Start Exchange triggered";
|
AppCore::getInstance()->SingleExchange();
|
||||||
|
});
|
||||||
|
// 连续对话功能 TODO: 待开发
|
||||||
|
connect(startContinueExchangeAction, &QAction::triggered, this, []() {
|
||||||
|
qDebug() << "Start ContinueExchange triggered";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user