refactor: 将系统代理操作改为异步执行

This commit is contained in:
Chenx Dust
2026-07-27 16:55:50 +08:00
parent 32cfc10942
commit c644cfe41b
7 changed files with 334 additions and 88 deletions

View File

@@ -21,6 +21,7 @@ message("PROJ_VER=${PROJ_VER}")
find_package(Qt6 COMPONENTS
Core
Concurrent
Gui
Widgets
Network
@@ -104,6 +105,7 @@ add_subdirectory(singleapplication)
target_link_libraries(${PROJECT_NAME}
Qt::Core
Qt::Concurrent
Qt::Gui
Qt::Widgets
Qt::Network
@@ -156,6 +158,6 @@ if(BUILD_TESTING)
utils/proxysettings.cpp
)
target_include_directories(systemproxysession_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(systemproxysession_test Qt::Core Qt::Network Qt::Widgets ${PLATFORM_LIBS})
target_link_libraries(systemproxysession_test Qt::Core Qt::Concurrent Qt::Network Qt::Widgets ${PLATFORM_LIBS})
add_test(NAME systemproxysession_test COMMAND systemproxysession_test)
endif()

View File

@@ -1,5 +1,7 @@
#include "systemproxysession.h"
#include <QtConcurrent>
#include "infrastructure/platformsystemproxybackend.h"
SystemProxySession::SystemProxySession(QObject *parent)
@@ -14,34 +16,121 @@ SystemProxySession::SystemProxySession(
: QObject(parent),
backend(std::move(backend))
{
connect(&operationWatcher, &QFutureWatcher<OperationResult>::finished,
this, &SystemProxySession::handleOperationFinished);
}
bool SystemProxySession::hasConflict(const SystemProxyConfig &config)
SystemProxySession::~SystemProxySession()
{
return backend->hasConflict(config);
operationWatcher.waitForFinished();
}
void SystemProxySession::enable(const SystemProxyConfig &config)
bool SystemProxySession::checkConflict(const SystemProxyConfig &config)
{
backend->apply(config);
if (!enabled)
{
enabled = true;
emit enabledChanged(true);
}
return startOperation(Operation::CheckConflict, config);
}
void SystemProxySession::disable()
bool SystemProxySession::enable(const SystemProxyConfig &config)
{
backend->clear();
if (enabled)
{
enabled = false;
emit enabledChanged(false);
}
return startOperation(Operation::Enable, config);
}
bool SystemProxySession::disable()
{
return startOperation(Operation::Disable);
}
bool SystemProxySession::isEnabled() const
{
return enabled;
}
bool SystemProxySession::isBusy() const
{
return currentOperation != Operation::None;
}
void SystemProxySession::clearBeforeShutdown()
{
const Operation operation = currentOperation;
disconnect(&operationWatcher, nullptr, this, nullptr);
operationWatcher.waitForFinished();
if (operation != Operation::Disable
&& (enabled || operation == Operation::Enable))
{
backend->clear();
}
currentOperation = Operation::None;
enabled = false;
}
bool SystemProxySession::startOperation(Operation operation, const SystemProxyConfig &config)
{
if (isBusy())
{
return false;
}
currentOperation = operation;
emit busyChanged(true);
SystemProxyBackend *proxyBackend = backend.get();
operationWatcher.setFuture(QtConcurrent::run(
[proxyBackend, operation, config]()
{
OperationResult result{operation};
switch (operation)
{
case Operation::CheckConflict:
result.conflict = proxyBackend->hasConflict(config);
break;
case Operation::Enable:
proxyBackend->apply(config);
break;
case Operation::Disable:
proxyBackend->clear();
break;
case Operation::None:
break;
}
return result;
}
));
return true;
}
void SystemProxySession::handleOperationFinished()
{
const OperationResult result = operationWatcher.result();
currentOperation = Operation::None;
bool stateChanged = false;
if (result.operation == Operation::Enable && !enabled)
{
enabled = true;
stateChanged = true;
}
else if (result.operation == Operation::Disable && enabled)
{
enabled = false;
stateChanged = true;
}
emit busyChanged(false);
if (stateChanged)
{
emit enabledChanged(enabled);
}
if (result.operation == Operation::CheckConflict)
{
emit conflictCheckFinished(result.conflict);
}
else
{
emit operationFinished(enabled);
}
}

View File

@@ -3,6 +3,7 @@
#include <memory>
#include <QFutureWatcher>
#include <QObject>
#include "systemproxybackend.h"
@@ -14,17 +15,42 @@ Q_OBJECT
public:
explicit SystemProxySession(QObject *parent = nullptr);
explicit SystemProxySession(std::unique_ptr<SystemProxyBackend> backend, QObject *parent = nullptr);
~SystemProxySession() override;
bool hasConflict(const SystemProxyConfig &config);
void enable(const SystemProxyConfig &config);
void disable();
bool checkConflict(const SystemProxyConfig &config);
bool enable(const SystemProxyConfig &config);
bool disable();
bool isEnabled() const;
bool isBusy() const;
void clearBeforeShutdown();
signals:
void enabledChanged(bool enabled);
void busyChanged(bool busy);
void conflictCheckFinished(bool conflict);
void operationFinished(bool enabled);
private:
enum class Operation
{
None,
CheckConflict,
Enable,
Disable
};
struct OperationResult
{
Operation operation;
bool conflict = false;
};
bool startOperation(Operation operation, const SystemProxyConfig &config = {});
void handleOperationFinished();
std::unique_ptr<SystemProxyBackend> backend;
QFutureWatcher<OperationResult> operationWatcher;
Operation currentOperation = Operation::None;
bool enabled = false;
};

View File

@@ -40,6 +40,29 @@ MainWindow::MainWindow(QWidget *parent) :
ui->setupUi(this);
systemProxySession = new SystemProxySession(this);
connect(systemProxySession, &SystemProxySession::busyChanged, this,
[this](bool busy)
{
ui->pushButton2->setEnabled(!busy);
ui->disableProxyAction->setEnabled(!busy);
});
connect(systemProxySession, &SystemProxySession::enabledChanged, this,
[this](bool enabled)
{
ui->pushButton2->setText(enabled ? "清除系统代理" : "设置系统代理");
if (!enabled && connectionSession != nullptr && !connectionSession->isActive())
{
ui->pushButton2->hide();
}
});
connect(systemProxySession, &SystemProxySession::operationFinished, this,
[this](bool enabled)
{
if (enabled && connectionSession != nullptr && !connectionSession->isActive())
{
systemProxySession->disable();
}
});
setupTrayIcon();
setupProfileMenu();
@@ -101,16 +124,16 @@ MainWindow::MainWindow(QWidget *parent) :
return;
}
if (systemProxySession->isEnabled())
{
ui->pushButton2->click();
}
else
{
systemProxySession->disable();
}
addLog("已清理系统代理设置");
connect(systemProxySession, &SystemProxySession::operationFinished, this,
[this](bool enabled)
{
if (!enabled)
{
addLog("已清理系统代理设置");
}
},
Qt::SingleShotConnection);
systemProxySession->disable();
});
// 文件-清理登录数据
@@ -812,9 +835,9 @@ void MainWindow::cleanUpWhenQuit()
settings->sync();
// 清除系统代理
if (systemProxySession != nullptr && systemProxySession->isEnabled())
if (systemProxySession != nullptr)
{
systemProxySession->disable();
systemProxySession->clearBeforeShutdown();
}
}

View File

@@ -1,7 +1,11 @@
#include <functional>
#include <memory>
#include <QCoreApplication>
#include <QDebug>
#include <QElapsedTimer>
#include <QSemaphore>
#include <QThread>
#include "application/systemproxysession.h"
@@ -15,6 +19,8 @@ public:
int applyCalls = 0;
int clearCalls = 0;
SystemProxyConfig lastConfig;
QSemaphore operationStarted;
QSemaphore allowOperationToFinish;
bool hasConflict(const SystemProxyConfig &config) override
{
@@ -27,6 +33,8 @@ public:
{
++applyCalls;
lastConfig = config;
operationStarted.release();
allowOperationToFinish.acquire();
}
void clear() override
@@ -35,7 +43,19 @@ public:
}
};
bool delegatesPlatformOperationsAndTracksOwnedState()
bool waitUntil(const std::function<bool()> &condition)
{
QElapsedTimer timer;
timer.start();
while (!condition() && timer.elapsed() < 1000)
{
QCoreApplication::processEvents();
QThread::msleep(1);
}
return condition();
}
bool delegatesPlatformOperationsAsynchronouslyAndTracksOwnedState()
{
auto backend = std::make_unique<FakeSystemProxyBackend>();
FakeSystemProxyBackend *fake = backend.get();
@@ -43,38 +63,74 @@ bool delegatesPlatformOperationsAndTracksOwnedState()
SystemProxySession session(std::move(backend));
const SystemProxyConfig config{1081, 1080, "localhost"};
if (!session.hasConflict(config)
bool conflictResult = false;
QObject::connect(&session, &SystemProxySession::conflictCheckFinished,
[&](bool conflict) { conflictResult = conflict; });
if (!session.checkConflict(config)
|| !waitUntil([&]() { return !session.isBusy(); })
|| !conflictResult
|| fake->conflictChecks != 1
|| session.isEnabled())
{
qCritical() << "delegatesPlatformOperationsAndTracksOwnedState failed at conflict check";
qCritical() << "delegatesPlatformOperationsAsynchronouslyAndTracksOwnedState failed at conflict check";
return false;
}
session.enable(config);
if (!session.isEnabled()
if (!session.enable(config)
|| !fake->operationStarted.tryAcquire(1, 1000)
|| !session.isBusy()
|| session.isEnabled()
|| session.disable())
{
qCritical() << "delegatesPlatformOperationsAsynchronouslyAndTracksOwnedState failed while enabling";
return false;
}
fake->allowOperationToFinish.release();
if (!waitUntil([&]() { return !session.isBusy(); })
|| !session.isEnabled()
|| fake->applyCalls != 1
|| fake->lastConfig.httpPort != 1081
|| fake->lastConfig.socksPort != 1080
|| fake->lastConfig.bypass != "localhost")
{
qCritical() << "delegatesPlatformOperationsAndTracksOwnedState failed at enable";
qCritical() << "delegatesPlatformOperationsAsynchronouslyAndTracksOwnedState failed after enabling";
return false;
}
session.disable();
if (session.isEnabled() || fake->clearCalls != 1)
if (!session.disable()
|| !waitUntil([&]() { return !session.isBusy(); })
|| session.isEnabled()
|| fake->clearCalls != 1)
{
qCritical() << "delegatesPlatformOperationsAndTracksOwnedState failed at disable";
qCritical() << "delegatesPlatformOperationsAsynchronouslyAndTracksOwnedState failed at disable";
return false;
}
session.disable();
if (fake->clearCalls != 2)
if (!session.disable()
|| !waitUntil([&]() { return !session.isBusy(); })
|| fake->clearCalls != 2)
{
qCritical() << "disable must also support clearing externally-owned proxy state";
return false;
}
if (!session.enable(config)
|| !fake->operationStarted.tryAcquire(1, 1000))
{
qCritical() << "clearBeforeShutdown failed to start enable";
return false;
}
fake->allowOperationToFinish.release();
session.clearBeforeShutdown();
if (session.isBusy()
|| session.isEnabled()
|| fake->applyCalls != 2
|| fake->clearCalls != 3)
{
qCritical() << "clearBeforeShutdown must clear an in-flight enable";
return false;
}
return true;
}
}
@@ -82,5 +138,5 @@ bool delegatesPlatformOperationsAndTracksOwnedState()
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
return delegatesPlatformOperationsAndTracksOwnedState() ? 0 : 1;
return delegatesPlatformOperationsAsynchronouslyAndTracksOwnedState() ? 0 : 1;
}

View File

@@ -1,7 +1,10 @@
#include <QApplication>
#include <QCoreApplication>
#include <QProcess>
#include <QMessageBox>
#include <QSettings>
#include <QStandardPaths>
#include <QThread>
#include "utils.h"
#if defined(Q_OS_WINDOWS)
@@ -13,6 +16,31 @@
const QString macOSNetworkSetupPath = "/usr/sbin/networksetup";
namespace
{
void showProxyError(const QString &title, const QString &message)
{
auto *application = qobject_cast<QApplication *>(QCoreApplication::instance());
if (application == nullptr)
{
qWarning() << title << message;
return;
}
if (QThread::currentThread() == application->thread())
{
QMessageBox::critical(nullptr, title, message);
return;
}
QMetaObject::invokeMethod(
application,
[title, message]() { QMessageBox::critical(nullptr, title, message); },
Qt::QueuedConnection
);
}
}
void windowsSetProxyForAllConnections(const QString &proxyServer, const QString &bypass)
{
#if defined(Q_OS_WINDOWS)
@@ -135,12 +163,12 @@ QStringList macOSGetActiveNetworkServices()
process.waitForFinished();
if (process.error() != QProcess::UnknownError)
{
QMessageBox::critical(nullptr, "获取网络服务失败", "执行命令失败:" + process.errorString());
showProxyError("获取网络服务失败", "执行命令失败:" + process.errorString());
return {};
}
if (process.exitCode() != 0)
{
QMessageBox::critical(nullptr, "获取网络服务失败", "无法获取网络服务:" + process.readAllStandardError());
showProxyError("获取网络服务失败", "无法获取网络服务:" + process.readAllStandardError());
return {};
}
/*
@@ -199,12 +227,12 @@ bool macOSIsSystemProxySet(macOSProxyType proxyType, const QString networkServic
process.waitForFinished();
if (process.error() != QProcess::UnknownError)
{
QMessageBox::critical(nullptr, "获取系统代理设置失败", "执行命令失败:" + process.errorString());
showProxyError("获取系统代理设置失败", "执行命令失败:" + process.errorString());
return true;
}
if (process.exitCode() != 0)
{
QMessageBox::critical(nullptr, "获取系统代理设置失败", "无法获取系统代理设置:" + process.readAllStandardError());
showProxyError("获取系统代理设置失败", "无法获取系统代理设置:" + process.readAllStandardError());
return true;
}
QString output = process.readAllStandardOutput();
@@ -241,12 +269,12 @@ void macOSSetSystemProxy(macOSProxyType proxyType, const QString &networkService
setProcess.waitForFinished();
if (setProcess.error() != QProcess::UnknownError)
{
QMessageBox::critical(nullptr, "设置系统代理失败", "执行命令失败:" + setProcess.errorString());
showProxyError("设置系统代理失败", "执行命令失败:" + setProcess.errorString());
return;
}
if (setProcess.exitCode() != 0)
{
QMessageBox::critical(nullptr, "设置系统代理失败", "无法设置系统代理:" + setProcess.readAllStandardError());
showProxyError("设置系统代理失败", "无法设置系统代理:" + setProcess.readAllStandardError());
return;
}
enableArgs << networkService << "on";
@@ -255,12 +283,12 @@ void macOSSetSystemProxy(macOSProxyType proxyType, const QString &networkService
enableProcess.waitForFinished();
if (enableProcess.error() != QProcess::UnknownError)
{
QMessageBox::critical(nullptr, "启用系统代理失败", "执行命令失败:" + enableProcess.errorString());
showProxyError("启用系统代理失败", "执行命令失败:" + enableProcess.errorString());
return;
}
if (enableProcess.exitCode() != 0)
{
QMessageBox::critical(nullptr, "启用系统代理失败", "无法启用系统代理:" + enableProcess.readAllStandardError());
showProxyError("启用系统代理失败", "无法启用系统代理:" + enableProcess.readAllStandardError());
return;
}
}
@@ -286,12 +314,12 @@ void macOSDisableSystemProxy(macOSProxyType proxyType, const QString &networkSer
process.waitForFinished();
if (process.error() != QProcess::UnknownError)
{
QMessageBox::critical(nullptr, "禁用系统代理失败", "执行命令失败:" + process.errorString());
showProxyError("禁用系统代理失败", "执行命令失败:" + process.errorString());
return;
}
if (process.exitCode() != 0)
{
QMessageBox::critical(nullptr, "禁用系统代理失败", "无法禁用系统代理:" + process.readAllStandardError());
showProxyError("禁用系统代理失败", "无法禁用系统代理:" + process.readAllStandardError());
return;
}
}
@@ -307,12 +335,12 @@ void macOSSetProxyBypass(const QString &networkService, const QString &bypass)
process.waitForFinished();
if (process.error() != QProcess::UnknownError)
{
QMessageBox::critical(nullptr, "设置代理绕过失败", "执行命令失败:" + process.errorString());
showProxyError("设置代理绕过失败", "执行命令失败:" + process.errorString());
return;
}
if (process.exitCode() != 0)
{
QMessageBox::critical(nullptr, "设置代理绕过失败", "无法设置代理绕过:" + process.readAllStandardError());
showProxyError("设置代理绕过失败", "无法设置代理绕过:" + process.readAllStandardError());
return;
}
}
@@ -427,7 +455,7 @@ void linuxSetSystemProxy(const QString &proxyServer, int httpPort, int socksPort
if (results.count(true) != actions.size())
{
QMessageBox::critical(nullptr, "设置系统代理失败", "存在失败的命令");
showProxyError("设置系统代理失败", "存在失败的命令");
}
}

View File

@@ -283,6 +283,11 @@ void MainWindow::initZjuConnect()
connect(ui->pushButton2, &QPushButton::clicked,
[&]()
{
if (systemProxySession->isBusy())
{
return;
}
if (!systemProxySession->isEnabled())
{
int http_port = settings->value("ZJUConnect/HTTPPort").toInt();
@@ -292,44 +297,61 @@ void MainWindow::initZjuConnect()
socks_port,
settings->value("Common/SystemProxyBypass").toString()
};
if (systemProxySession->hasConflict(proxyConfig))
{
bool suppressed = settings->value("Common/SuppressProxyOverrideWarning", false).toBool();
if (suppressed) {
addLog("跳过系统代理覆盖警告,因为已设置了不再提示");
} else {
QMessageBox msgBox(QMessageBox::Warning, "警告",
"当前已存在系统代理配置(可能是 Clash 或其它代理软件)\n是否覆盖当前系统代理配置?",
QMessageBox::Yes | QMessageBox::No, this);
QCheckBox *dontShowCheckBox = new QCheckBox("不再提示");
msgBox.setCheckBox(dontShowCheckBox);
if (msgBox.exec() == QMessageBox::No)
connect(systemProxySession, &SystemProxySession::conflictCheckFinished, this,
[this, proxyConfig, http_port, socks_port](bool conflict)
{
return;
}
if (!connectionSession->isActive())
{
return;
}
if (dontShowCheckBox->isChecked())
{
settings->setValue("Common/SuppressProxyOverrideWarning", true);
settings->sync();
}
}
}
if (conflict)
{
bool suppressed = settings->value(
"Common/SuppressProxyOverrideWarning", false
).toBool();
if (suppressed)
{
addLog("跳过系统代理覆盖警告,因为已设置了不再提示");
}
else
{
QMessageBox msgBox(
QMessageBox::Warning,
"警告",
"当前已存在系统代理配置(可能是 Clash 或其它代理软件)\n是否覆盖当前系统代理配置?",
QMessageBox::Yes | QMessageBox::No,
this
);
addLog("设置系统代理HTTP端口 " + QString::number(http_port) + "SOCKS5 端口 " + QString::number(socks_port));
systemProxySession->enable(proxyConfig);
ui->pushButton2->setText("清除系统代理");
QCheckBox *dontShowCheckBox = new QCheckBox("不再提示");
msgBox.setCheckBox(dontShowCheckBox);
if (msgBox.exec() == QMessageBox::No)
{
return;
}
if (dontShowCheckBox->isChecked())
{
settings->setValue("Common/SuppressProxyOverrideWarning", true);
settings->sync();
}
}
}
addLog(
"设置系统代理HTTP端口 " + QString::number(http_port)
+ "SOCKS5 端口 " + QString::number(socks_port)
);
systemProxySession->enable(proxyConfig);
},
Qt::SingleShotConnection);
systemProxySession->checkConflict(proxyConfig);
}
else
{
systemProxySession->disable();
ui->pushButton2->setText("设置系统代理");
if (!connectionSession->isActive())
{
ui->pushButton2->hide();
}
}
});