Files
EZ4Connect/utils/utils.cpp

519 lines
16 KiB
C++

#include <QMessageBox>
#include <QInputDialog>
#include <QLineEdit>
#include <QApplication>
#include <QNetworkInterface>
#include <QTextCodec>
#include <QSettings>
#include <QProcess>
#include <QtSystemDetection>
#include <QDir>
#include <QFileInfo>
#include <QCoreApplication>
#include <QStandardPaths>
#if defined(Q_OS_WIN)
#include <windows.h>
#include <shellapi.h>
#elif defined(Q_OS_UNIX)
#include <unistd.h>
#endif
#include "utils.h"
#include "profilemanager.h"
namespace
{
QString profileIdFromConfigPath(const QString &configPath)
{
QFileInfo info(configPath);
if (!info.exists() || info.suffix() != "ini")
{
return "custom";
}
if (info.fileName() == "state.ini")
{
return "custom";
}
QDir parentDir = info.dir();
if (parentDir.dirName() != "profiles")
{
return "custom";
}
return info.baseName();
}
}
QString Utils::consoleOutputToQString(const QByteArray &byteArray)
{
static QString codeName = "";
if (codeName == "UTF-8")
{
return QTextCodec::codecForName("UTF-8")->toUnicode(byteArray);;
}
else if (codeName == "GBK")
{
return QTextCodec::codecForName("GBK")->toUnicode(byteArray);;
}
else if (codeName == "locale")
{
return QString::fromLocal8Bit(byteArray);
}
else
{
QTextCodec *utf8Codec = QTextCodec::codecForName("UTF-8");
QString utf8Str = utf8Codec->toUnicode(byteArray);
QByteArray utf8ByteArrayBack = utf8Codec->fromUnicode(utf8Str);
QTextCodec *gbkCodec = QTextCodec::codecForName("GBK");
QString gbkStr = gbkCodec->toUnicode(byteArray);
QByteArray gbkByteArrayBack = gbkCodec->fromUnicode(gbkStr);
if (utf8ByteArrayBack == byteArray && gbkByteArrayBack != byteArray)
{
codeName = "UTF-8";
return utf8Str;
}
else if (gbkByteArrayBack == byteArray && utf8ByteArrayBack != byteArray)
{
codeName = "GBK";
return gbkStr;
}
else if (gbkByteArrayBack == byteArray && utf8ByteArrayBack == byteArray)
{
return utf8Str;
}
else
{
codeName = "locale";
return QString::fromLocal8Bit(byteArray);
}
}
}
QString Utils::getArgValue(const QStringList &args, const QString &key)
{
const QString prefix = key + "=";
for (int i = 0; i < args.size(); ++i)
{
const QString &arg = args.at(i);
if (arg == key)
{
if (i + 1 < args.size())
{
return args.at(i + 1);
}
return "";
}
if (arg.startsWith(prefix))
{
return arg.mid(prefix.size());
}
}
return "";
}
void Utils::setWidgetFixedWhenHidden(QWidget *widget)
{
QSizePolicy originPolicy = widget->sizePolicy();
originPolicy.setRetainSizeWhenHidden(true);
widget->setSizePolicy(originPolicy);
}
void Utils::showAboutMessageBox(QWidget *parent)
{
QMessageBox messageBox(parent);
messageBox.setWindowTitle("关于");
messageBox.setTextFormat(Qt::RichText);
QString aboutText = QApplication::applicationDisplayName() + " " + QApplication::applicationVersion() +
"<br>改进的 ZJU-Connect 图形界面" +
"<br>作者:<a href='https://github.com/chenx-dust'>Chenx Dust</a>" +
"<br>项目主页:<a href='https://github.com/" + REPO_NAME + "'>https://github.com/" + REPO_NAME + "</a>" +
"<br><br><b>致谢:</b>" +
"<br><br>ZJU-Connect-for-Windows" +
"<br>基于 Qt 编写的 ZJU 网络客户端" +
"<br>作者:<a href='https://myth.cx'>Myth</a>" +
"<br>项目主页:<a href='https://github.com/Mythologyli/ZJU-Connect-for-Windows'>https://github.com/Mythologyli/ZJU-Connect-for-Windows</a>" +
"<br><br>zju-connect" +
"<br>ZJU RVPN 客户端的 Go 语言实现" +
"<br>作者:<a href='https://myth.cx'>Myth</a>" +
"<br>项目主页:<a href='https://github.com/Mythologyli/zju-connect'>https://github.com/Mythologyli/zju-connect</a>" +
"<br><br>EasierConnect" +
"<br>EasyConnect 客户端的开源实现" +
"<br>作者:<a href='https://github.com/lyc8503'>lyc8503</a>" +
"<br>项目主页:<a href='https://github.com/lyc8503/EasierConnect'>https://github.com/lyc8503/EasierConnect</a>";
messageBox.setText(aboutText);
messageBox.setIconPixmap(QPixmap(":/resource/icon.png").scaled(
100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation
));
messageBox.exec();
}
QString Utils::getIpv4Address(const QString &interfaceName)
{
auto interfaces = QNetworkInterface::allInterfaces();
for (auto &singleInterface: interfaces)
{
if (singleInterface.humanReadableName() == interfaceName)
{
auto addresses =
singleInterface.addressEntries();
for (auto &address: addresses)
{
if (address.ip().protocol() == QAbstractSocket::IPv4Protocol)
{
return address.ip().toString();
}
}
}
}
return "";
}
QString macOSAppBundlePath()
{
#ifdef Q_OS_MAC
QDir dir = QDir(QCoreApplication::applicationDirPath());
dir.cdUp();
dir.cdUp();
QString absolutePath = dir.absolutePath();
// absolutePath will contain a "/" at the end,
// but we want the clean path to the .app bundle
if (absolutePath.length() > 0 && absolutePath.right(1) == "/")
{
absolutePath.chop(1);
}
return absolutePath;
#else
return "";
#endif
}
QString macOSAppBundleName()
{
#ifdef Q_OS_MAC
QString bundlePath = macOSAppBundlePath();
QFileInfo fileInfo(bundlePath);
return fileInfo.baseName();
#else
return "";
#endif
}
QString nativeAppPath()
{
return QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
}
bool Utils::isRunningAsAdmin()
{
#if defined(Q_OS_WIN)
BOOL isAdmin = FALSE;
PSID adminGroup = nullptr;
SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY;
if (AllocateAndInitializeSid(&ntAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroup))
{
CheckTokenMembership(nullptr, adminGroup, &isAdmin);
FreeSid(adminGroup);
}
return isAdmin;
#elif defined(Q_OS_UNIX)
return geteuid() == 0;
#else
return false;
#endif
}
bool Utils::relaunchAsAdmin()
{
QString program = QCoreApplication::applicationFilePath();
QStringList args = QCoreApplication::arguments();
if (!args.isEmpty())
{
args.removeFirst();
}
if (!args.contains("--connect"))
{
args << "--connect";
}
#if defined(Q_OS_WIN)
QStringList quotedArgs;
for (const QString &arg : args)
{
if (arg.contains(' '))
{
quotedArgs << "\"" + arg + "\"";
}
else
{
quotedArgs << arg;
}
}
QString arguments = quotedArgs.join(" ");
HINSTANCE res = ShellExecuteW(
nullptr,
L"runas",
reinterpret_cast<LPCWSTR>(program.utf16()),
reinterpret_cast<LPCWSTR>(arguments.utf16()),
nullptr,
SW_SHOWNORMAL
);
return reinterpret_cast<INT_PTR>(res) > 32;
#else
Q_UNUSED(program)
Q_UNUSED(args)
return false;
#endif
}
void Utils::setAutoStart(bool enable)
{
#if defined(Q_OS_WINDOWS)
QSettings autoStartSettings(R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run)",
QSettings::NativeFormat);
if (enable)
{
autoStartSettings.setValue(
QApplication::applicationName(),
nativeAppPath()
);
}
else
{
autoStartSettings.remove(QApplication::applicationName());
}
autoStartSettings.sync();
#elif defined(Q_OS_MACOS)
{
QStringList args;
args << "-e" << "tell application \"System Events\" to delete login item \"" + macOSAppBundleName() + "\"";
qDebug() << args;
QProcess process;
process.start("osascript", args);
process.waitForFinished();
QString error = process.readAllStandardError();
qDebug() << process.readAllStandardOutput();
qDebug() << error;
if (!enable && process.error() != QProcess::UnknownError)
{
QMessageBox::critical(nullptr, "取消开机自启动失败", "无法删除登录项:" + process.errorString());
return;
}
if (!enable && process.exitCode() != 0)
{
QMessageBox::critical(nullptr, "取消开机自启动失败", "无法删除登录项:" + error);
return;
}
}
if (enable)
{
QStringList args;
args << "-e" << "tell application \"System Events\" to make login item at end with properties {path:\"" +
macOSAppBundlePath() + "\", hidden:false}";
qDebug() << args;
QProcess process;
process.start("osascript", args);
process.waitForFinished();
qDebug() << process.readAllStandardOutput();
qDebug() << process.readAllStandardError();
if (process.error() != QProcess::UnknownError)
{
QMessageBox::critical(nullptr, "设置开机自启动失败", "无法创建登录项:" + process.errorString());
return;
}
if (process.exitCode() != 0)
{
QMessageBox::critical(nullptr, "设置开机自启动失败", "无法创建登录项:" + process.readAllStandardError());
return;
}
}
#elif defined(Q_OS_LINUX)
QString autostartPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/autostart/";
QDir dir(autostartPath);
QString desktopFilePath = autostartPath + QApplication::applicationName() + ".desktop";
QFile desktopFile(desktopFilePath);
if (dir.exists() && desktopFile.exists())
{
if (!desktopFile.remove())
{
QMessageBox::critical(nullptr, "取消开机自启动失败", "无法删除 .desktop 文件:" + desktopFilePath);
return;
}
}
if (enable)
{
if (!dir.exists())
{
if (!dir.mkpath("."))
{
QMessageBox::critical(nullptr, "设置开机自启动失败", "无法创建 autostart 目录:" + autostartPath);
return;
}
}
if (!desktopFile.open(QIODevice::WriteOnly | QIODevice::Text))
{
QMessageBox::critical(nullptr, "设置开机自启动失败", "无法创建 .desktop 文件:" + desktopFilePath);
return;
}
QTextStream out(&desktopFile);
out << "[Desktop Entry]\n";
out << "Type=Application\n";
out << "Name=" << QApplication::applicationName() << "\n";
out << "Exec=" << nativeAppPath() << "\n";
out << "X-GNOME-Autostart-enabled=true\n";
desktopFile.close();
}
#endif
}
bool Utils::credentialCheck(const QString &username, const QString &password)
{
if (username.isEmpty() || password.isEmpty())
{
int status = QMessageBox::warning(nullptr, "警告", "账号或密码为空!\n\n是否继续?", QMessageBox::Ok, QMessageBox::Cancel);
return status == QMessageBox::Ok;
}
bool asciiOnly = true;
for (QChar c: username)
{
if (c.unicode() > 127)
{
asciiOnly = false;
break;
}
}
for (QChar c: password)
{
if (c.unicode() > 127)
{
asciiOnly = false;
break;
}
}
if (!asciiOnly)
{
int status = QMessageBox::warning(nullptr, "警告", "账号或密码存在非 ASCII 字符!\n建议检查输入法设置。\n\n是否继续?", QMessageBox::Ok, QMessageBox::Cancel);
return status == QMessageBox::Ok;
}
return true;
}
void Utils::clearClientData(const QString &profileId)
{
QFile::remove(getClientDataPath(profileId));
}
QString Utils::getClientDataPath(const QString &profileId)
{
QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
QDir dir(defaultPath);
if (!dir.exists())
{
dir.mkpath(".");
}
QDir profileDir(dir.filePath("profiles/" + profileId));
if (!profileDir.exists())
{
profileDir.mkpath(".");
}
QFile clientDataFile(profileDir.filePath("client-data.json"));
if (!clientDataFile.exists() && clientDataFile.open(QIODevice::WriteOnly))
{
clientDataFile.write("{}");
clientDataFile.close();
}
return profileDir.filePath("client-data.json");
}
QString Utils::getLogFilePath()
{
QString logPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
QDir logDir(logPath);
if (!logDir.exists())
{
logDir.mkpath(".");
}
return logDir.filePath("zjuconnect.log");
}
void Utils::resetDefaultSettings(QSettings& settings)
{
settings.setValue("Credential/Username", "");
settings.setValue("Credential/Password", "");
settings.setValue("Credential/TOTPSecret", "");
settings.setValue("Common/ConnectAfterStart", false);
settings.setValue("Common/CheckUpdateAfterStart", false);
settings.setValue("Common/AutoSetProxy", false);
settings.setValue("Common/ReconnectTime", 1);
settings.setValue("Common/AutoReconnect", false);
settings.setValue("Common/SystemProxyBypass", "");
settings.setValue("ZJUConnect/ServerAddress", "trust.hitsz.edu.cn");
settings.setValue("ZJUConnect/ServerPort", 443);
settings.setValue("ZJUConnect/DNS", "");
settings.setValue("ZJUConnect/DNSAuto", true);
settings.setValue("ZJUConnect/SecondaryDNS", "");
settings.setValue("ZJUConnect/DNSTTL", 3600);
settings.setValue("ZJUConnect/SOCKS5Port", 11080);
settings.setValue("ZJUConnect/HTTPPort", 11081);
settings.setValue("ZJUConnect/ShadowsocksURL", "");
settings.setValue("ZJUConnect/DialDirectProxy", "");
settings.setValue("ZJUConnect/UpdateBestNodesInterval", 300);
settings.setValue("ZJUConnect/Protocol", "atrust");
settings.setValue("ZJUConnect/LoginDomain", "hitcas");
settings.setValue("ZJUConnect/AuthType", "cas");
settings.setValue("ZJUConnect/CasLoginURL", "");
settings.setValue("ZJUConnect/PhoneCountryCode", "86");
settings.setValue("ZJUConnect/PhoneNumber", "");
settings.setValue("ZJUConnect/MultiLine", false);
settings.setValue("ZJUConnect/KeepAlive", false);
settings.setValue("ZJUConnect/KeepAliveURL", "");
settings.setValue("ZJUConnect/OutsideAccess", false);
settings.setValue("ZJUConnect/SkipDomainResource", false);
settings.setValue("ZJUConnect/DisableServerConfig", false);
settings.setValue("ZJUConnect/ProxyAll", false);
settings.setValue("ZJUConnect/DisableZJUDNS", false);
settings.setValue("ZJUConnect/ZJUDefault", false);
settings.setValue("ZJUConnect/Debug", false);
settings.setValue("ZJUConnect/TUNMode", false);
settings.setValue("ZJUConnect/AddRoute", false);
settings.setValue("ZJUConnect/DNSHijack", false);
settings.setValue("ZJUConnect/FakeIP", false);
settings.setValue("ZJUConnect/TCPTunnelMode", false);
settings.setValue("ZJUConnect/TCPPortForwarding", "");
settings.setValue("ZJUConnect/UDPPortForwarding", "");
settings.setValue("ZJUConnect/CustomDNS", "");
settings.setValue("ZJUConnect/CustomProxyDomain", "");
settings.setValue("ZJUConnect/ExtraArguments", "");
settings.setValue("Common/ConfigVersion", Utils::CONFIG_VERSION);
}