mirror of
https://github.com/chenx-dust/EZ4Connect.git
synced 2026-09-20 10:23:33 +08:00
372 lines
16 KiB
C++
372 lines
16 KiB
C++
#include <QMessageBox>
|
||
#include <QCheckBox>
|
||
#include <QDialogButtonBox>
|
||
#include <QDebug>
|
||
#include <QInputDialog>
|
||
#include <QLabel>
|
||
#include <QLineEdit>
|
||
#include <QVBoxLayout>
|
||
#include <QApplication>
|
||
|
||
#include "mainwindow.h"
|
||
#include "application/applicationlogger.h"
|
||
#include "infrastructure/corelogfile.h"
|
||
#include "ui_mainwindow.h"
|
||
#include "sudowindow/sudowindow.h"
|
||
#include "loginwindow/loginwindow.h"
|
||
#include "infrastructure/settingsprofileloader.h"
|
||
#include "utils/utils.h"
|
||
#include "zjuconnectcontroller/zjuconnectcontroller.h"
|
||
|
||
void MainWindow::initZjuConnect()
|
||
{
|
||
if (connectionSession != nullptr)
|
||
{
|
||
return;
|
||
}
|
||
|
||
clearLog();
|
||
|
||
connectionSession = new ConnectionSession(this);
|
||
resetZjuConnectUi();
|
||
|
||
// 连接服务器
|
||
connect(connectionSession, &ConnectionSession::outputRead,
|
||
applicationLogger, &ApplicationLogger::appendCoreOutput);
|
||
connect(connectionSession, &ConnectionSession::outputRead,
|
||
coreLogFile, &CoreLogFile::appendOutput);
|
||
|
||
connect(connectionSession, &ConnectionSession::savedSudoPasswordRejected, this,
|
||
[&]() { qWarning().noquote() << "sudo 密码可能有误,不使用记住的密码"; });
|
||
|
||
connect(connectionSession, &ConnectionSession::askSudoPass, this,
|
||
[&]()
|
||
{
|
||
sudoWindow = new SudoWindow(this);
|
||
connect(sudoWindow, &SudoWindow::sudo, this, [&](const QString &password, bool save)
|
||
{
|
||
connectionSession->submitSudoPassword(password, save);
|
||
});
|
||
sudoWindow->show();
|
||
});
|
||
|
||
connect(connectionSession, &ConnectionSession::graphCaptcha, this,
|
||
[&](const QString &graphFile) {
|
||
qInfo().noquote() << "需要图形验证码";
|
||
graphCaptchaWindow = new GraphCaptchaWindow(this);
|
||
graphCaptchaWindow->setGraph(graphFile);
|
||
graphCaptchaWindow->show();
|
||
connect(graphCaptchaWindow, &GraphCaptchaWindow::finishCaptcha, this, [&](const QByteArray &captcha) {
|
||
qInfo().noquote() << "图形验证码已提交";
|
||
connectionSession->submitInput(captcha + "\n");
|
||
});
|
||
});
|
||
|
||
connect(connectionSession, &ConnectionSession::smsCode, this, [&](bool showSkipSecondaryAuthOption) {
|
||
qInfo().noquote() << "需要短信验证码";
|
||
|
||
QDialog smsCodeDialog(this);
|
||
smsCodeDialog.setWindowTitle("短信验证码");
|
||
|
||
auto *dialogLayout = new QVBoxLayout(&smsCodeDialog);
|
||
dialogLayout->addWidget(new QLabel("请输入短信验证码:", &smsCodeDialog));
|
||
|
||
auto *smsCodeLineEdit = new QLineEdit(&smsCodeDialog);
|
||
dialogLayout->addWidget(smsCodeLineEdit);
|
||
|
||
auto *skipSecondaryAuthCheckBox = new QCheckBox("跳过以后的短信验证", &smsCodeDialog);
|
||
skipSecondaryAuthCheckBox->setVisible(showSkipSecondaryAuthOption);
|
||
dialogLayout->addWidget(skipSecondaryAuthCheckBox);
|
||
|
||
auto *buttonBox = new QDialogButtonBox(
|
||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &smsCodeDialog);
|
||
connect(buttonBox, &QDialogButtonBox::accepted, &smsCodeDialog, &QDialog::accept);
|
||
connect(buttonBox, &QDialogButtonBox::rejected, &smsCodeDialog, &QDialog::reject);
|
||
dialogLayout->addWidget(buttonBox);
|
||
|
||
QString smsCode;
|
||
bool skipSecondaryAuth = false;
|
||
const bool smsCodeAccepted = smsCodeDialog.exec() == QDialog::Accepted;
|
||
if (smsCodeAccepted)
|
||
{
|
||
smsCode = smsCodeLineEdit->text();
|
||
skipSecondaryAuth = showSkipSecondaryAuthOption && skipSecondaryAuthCheckBox->isChecked();
|
||
}
|
||
|
||
if (smsCodeAccepted)
|
||
{
|
||
qInfo().noquote()
|
||
<< (skipSecondaryAuth ? "短信验证码已提交(跳过以后的短信验证)" : "短信验证码已提交");
|
||
}
|
||
else
|
||
{
|
||
qInfo().noquote() << "短信验证码输入已取消";
|
||
}
|
||
QByteArray smsCodeInput = smsCode.toLocal8Bit();
|
||
if (skipSecondaryAuth)
|
||
{
|
||
smsCodeInput.prepend('$');
|
||
}
|
||
connectionSession->submitInput(smsCodeInput + "\n");
|
||
});
|
||
|
||
connect(connectionSession, &ConnectionSession::totpCode, this, [&]() {
|
||
qInfo().noquote() << "需要 TOTP 验证码";
|
||
bool accepted = false;
|
||
QString totp = QInputDialog::getText(
|
||
this, "TOTP 验证码", "请输入 TOTP 验证码:", QLineEdit::Normal, "", &accepted);
|
||
qInfo().noquote() << (accepted ? "TOTP 验证码已提交" : "TOTP 验证码输入已取消");
|
||
connectionSession->submitInput(totp.toLocal8Bit() + "\n");
|
||
});
|
||
|
||
connect(connectionSession, &ConnectionSession::ssoAuth, this, [&]() {
|
||
ssoLoginWebView = new SsoLoginWebView(this);
|
||
connect(ssoLoginWebView, &SsoLoginWebView::loginCompleted,
|
||
[=](const QString &url) { connectionSession->submitInput(url.toLocal8Bit() + "\n"); });
|
||
|
||
QString serverHost = settings->value("ZJUConnect/ServerAddress", "trust.hitsz.edu.cn").toString();
|
||
int serverPort = settings->value("ZJUConnect/ServerPort", 443).toInt();
|
||
if (serverPort != 443)
|
||
{
|
||
serverHost += ":" + QString::number(serverPort);
|
||
}
|
||
QString ssoUrl = settings->value("ZJUConnect/LoginURL").toString();
|
||
if (ssoUrl.isEmpty())
|
||
ssoUrl = "https://" + serverHost +
|
||
"/passport/v1/public/casLogin?sfDomain=" + settings->value("ZJUConnect/LoginDomain").toString();
|
||
if (ssoUrl.startsWith("/"))
|
||
ssoUrl = "https://" + serverHost + ssoUrl;
|
||
|
||
qInfo().noquote() << QStringLiteral("单点登录:") + ssoUrl;
|
||
ssoLoginWebView->setInitialUrl(QUrl::fromUserInput(ssoUrl));
|
||
ssoLoginWebView->setCallbackServerHost(serverHost);
|
||
ssoLoginWebView->show();
|
||
});
|
||
|
||
connect(connectionSession, &ConnectionSession::reconnectScheduled, this, [&](int)
|
||
{
|
||
qInfo().noquote() << "正在尝试重新连接...";
|
||
});
|
||
|
||
connect(connectionSession, &ConnectionSession::finished, this, [&](ZJU_ERROR error)
|
||
{
|
||
qInfo().noquote() << "VPN 断开!";
|
||
if (error != ZJU_ERROR::NONE)
|
||
{
|
||
showNotification("VPN", "VPN 意外断开!", QSystemTrayIcon::MessageIcon::Warning);
|
||
}
|
||
ui->pushButton1->setText("连接服务器");
|
||
trayConnectAction->setText("连接服务器");
|
||
if (systemProxySession->isEnabled())
|
||
{
|
||
ui->pushButton2->click();
|
||
}
|
||
ui->pushButton2->hide();
|
||
|
||
switch (error)
|
||
{
|
||
case ZJU_ERROR::INVALID_DETAIL:
|
||
QMessageBox::critical(this, "错误", "登录失败!\n请检查设置中的网络账号和密码是否设置正确。");
|
||
break;
|
||
case ZJU_ERROR::BRUTE_FORCE:
|
||
QMessageBox::critical(this, "错误", "登录失败!\n登录尝试过于频繁,IP 被风控,请稍后重试或换用 EasyConnect。");
|
||
break;
|
||
case ZJU_ERROR::OTHER_LOGIN_FAILED:
|
||
QMessageBox::critical(this, "错误", "登录失败!\n未知原因,可将日志反馈给开发者以便调查。");
|
||
break;
|
||
case ZJU_ERROR::ACCESS_DENIED:
|
||
QMessageBox::critical(this, "错误", "权限不足!\n请关闭程序,点击右键以管理员身份运行。");
|
||
break;
|
||
case ZJU_ERROR::LISTEN_FAILED:
|
||
QMessageBox::critical(this, "错误", "监听失败!\n请关闭占用端口的程序(如残留的 zju-connect.exe),或者监听其它端口。");
|
||
break;
|
||
case ZJU_ERROR::CLIENT_FAILED:
|
||
QMessageBox::critical(this, "错误", "连接失败!\n可能是响应超时,请检查本地网络配置是否正常,服务器设置是否正确。");
|
||
break;
|
||
case ZJU_ERROR::CAPTCHA_FAILED:
|
||
QMessageBox::critical(this, "错误", "登录失败!\n验证码问题,可能是已验证码过期或者有误。");
|
||
break;
|
||
case ZJU_ERROR::PROGRAM_NOT_FOUND:
|
||
QMessageBox::critical(this, "错误", "程序未找到!\n请检查核心是否在正确路径下,检查是否解压在当前目录下。");
|
||
break;
|
||
case ZJU_ERROR::INTERACTIVE_ERROR:
|
||
QMessageBox::critical(this, "错误", "登录失败!\n请检查您的输入是否正确,检查是否完成 SSO 登录。");
|
||
break;
|
||
case ZJU_ERROR::AUTH_NOT_AVAILABLE:
|
||
QMessageBox::critical(this, "错误", "认证方式/登录域不可用!\n请通过“获取认证方式”按钮配置认证方式。");
|
||
break;
|
||
case ZJU_ERROR::AUTH_EXPIRED:
|
||
QMessageBox::critical(this, "错误", "认证已过期!\n请重新登录。");
|
||
break;
|
||
case ZJU_ERROR::OTHER:
|
||
QMessageBox::critical(this, "错误", "其它错误!\n未知原因,可将日志反馈给开发者以便调查。");
|
||
break;
|
||
case ZJU_ERROR::NONE:
|
||
default:
|
||
break;
|
||
}
|
||
});
|
||
|
||
connect(ui->pushButton1, &QPushButton::clicked,
|
||
[&]()
|
||
{
|
||
if (!connectionSession->isActive())
|
||
{
|
||
if (settings->contains("ZJUConnect/ServerAddress") &&
|
||
settings->value("ZJUConnect/ServerAddress").toString().isEmpty())
|
||
{
|
||
QMessageBox::critical(this, "错误", "服务器地址不能为空");
|
||
return;
|
||
}
|
||
|
||
QString username_ = settings->value("Credential/Username", "").toString();
|
||
QString password_ = QByteArray::fromBase64(settings->value("Credential/Password", "").toString().toUtf8());
|
||
QString protocol = settings->value("ZJUConnect/Protocol", "easyconnect").toString();
|
||
QString authtype = settings->value("ZJUConnect/AuthType", "psw").toString();
|
||
|
||
#if defined(Q_OS_WIN)
|
||
// Linux/macOS will elevate only the core process via sudo in the controller.
|
||
if (settings->value("ZJUConnect/TUNMode").toBool() && !Utils::isRunningAsAdmin())
|
||
{
|
||
if (Utils::relaunchAsAdmin())
|
||
{
|
||
QApplication::quit();
|
||
}
|
||
else
|
||
{
|
||
QMessageBox::warning(this, "提升失败", "无法以管理员权限重新启动,请手动以管理员方式运行。");
|
||
}
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
auto startZjuConnect = [this](const QString &username, const QString &password) {
|
||
ui->pushButton1->setText("断开服务器");
|
||
trayConnectAction->setText("断开服务器");
|
||
ui->pushButton2->show();
|
||
|
||
if (settings->value("Common/AutoSetProxy", false).toBool())
|
||
{
|
||
ui->pushButton2->click();
|
||
}
|
||
|
||
ConnectionProfile profile =
|
||
SettingsProfileLoader::load(*settings, currentProfileId, username, password);
|
||
profile.program = Utils::getCorePath();
|
||
const ReconnectPolicy reconnectPolicy{
|
||
settings->value("Common/AutoReconnect", false).toBool(),
|
||
settings->value("Common/ReconnectTime", 1).toInt() * 1000
|
||
};
|
||
connectionSession->start(profile, reconnectPolicy);
|
||
};
|
||
|
||
if (((protocol == "atrust" && authtype == "psw") ||
|
||
(protocol == "easyconnect" && settings->value("Credential/CertFile", "").toString().isEmpty())) &&
|
||
(username_.isEmpty() || password_.isEmpty()))
|
||
{
|
||
loginWindow = new LoginWindow(this);
|
||
loginWindow->setDetail(username_, password_);
|
||
|
||
connect(loginWindow, &LoginWindow::login, this,
|
||
[&, startZjuConnect](const QString& username, const QString& password, bool saveDetail)
|
||
{
|
||
if (saveDetail)
|
||
{
|
||
settings->setValue("Credential/Username", username);
|
||
settings->setValue("Credential/Password", QString(password.toUtf8().toBase64()));
|
||
settings->sync();
|
||
}
|
||
startZjuConnect(username, password);
|
||
}
|
||
);
|
||
loginWindow->show();
|
||
}
|
||
else
|
||
{
|
||
startZjuConnect(username_, password_);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
connectionSession->stop();
|
||
}
|
||
});
|
||
|
||
// 设置系统代理
|
||
connect(ui->pushButton2, &QPushButton::clicked,
|
||
[&]()
|
||
{
|
||
if (systemProxySession->isBusy())
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (!systemProxySession->isEnabled())
|
||
{
|
||
int http_port = settings->value("ZJUConnect/HTTPPort").toInt();
|
||
int socks_port = settings->value("ZJUConnect/SOCKS5Port").toInt();
|
||
const SystemProxyConfig proxyConfig{
|
||
http_port,
|
||
socks_port,
|
||
settings->value("Common/SystemProxyBypass").toString()
|
||
};
|
||
connect(systemProxySession, &SystemProxySession::conflictCheckFinished, this,
|
||
[this, proxyConfig, http_port, socks_port](bool conflict)
|
||
{
|
||
if (!connectionSession->isActive())
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (conflict)
|
||
{
|
||
bool suppressed = settings->value(
|
||
"Common/SuppressProxyOverrideWarning", false
|
||
).toBool();
|
||
if (suppressed)
|
||
{
|
||
qInfo().noquote() << "跳过系统代理覆盖警告,因为已设置了不再提示";
|
||
}
|
||
else
|
||
{
|
||
QMessageBox msgBox(
|
||
QMessageBox::Warning,
|
||
"警告",
|
||
"当前已存在系统代理配置(可能是 Clash 或其它代理软件)\n是否覆盖当前系统代理配置?",
|
||
QMessageBox::Yes | QMessageBox::No,
|
||
this
|
||
);
|
||
|
||
QCheckBox *dontShowCheckBox = new QCheckBox("不再提示");
|
||
msgBox.setCheckBox(dontShowCheckBox);
|
||
|
||
if (msgBox.exec() == QMessageBox::No)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (dontShowCheckBox->isChecked())
|
||
{
|
||
settings->setValue("Common/SuppressProxyOverrideWarning", true);
|
||
settings->sync();
|
||
}
|
||
}
|
||
}
|
||
|
||
qInfo().noquote()
|
||
<< "设置系统代理:HTTP端口 " + QString::number(http_port)
|
||
+ ",SOCKS5 端口 " + QString::number(socks_port);
|
||
systemProxySession->enable(proxyConfig);
|
||
},
|
||
Qt::SingleShotConnection);
|
||
systemProxySession->checkConflict(proxyConfig);
|
||
}
|
||
else
|
||
{
|
||
systemProxySession->disable();
|
||
}
|
||
});
|
||
|
||
emit SetModeFinished();
|
||
}
|