mirror of
https://github.com/outbackdingo/amnezia-client.git
synced 2026-01-27 02:18:09 +00:00
* There's a common issue of building iOS apps on Qt 6.8 because of new introduced ffmpeg dependency in multimedia Qt package ref: https://community.esri.com/t5/qt-maps-sdk-questions/build-failure-on-ios-with-qt-6-8/m-p/1548701#M5339 * Cmake related changes * Source code changes * Various entitlements * Ci-cd config update * Resources changes * Submodules updated * Remove me * QtWidget exclusion omitted * Distribution errors fixed * Outdated files deleted * macos_ne cmake fixed * fix: update provisioning profile specifiers for macOS network extension * fix: update provisioning profile specifiers and code sign flags for macOS build * Revert me (temporary 3rd-build commit pointer) * fix: Welcome screen fix * fix: ci/cd hanging forever fix * fix: Fixed error popup on macos on file save * refactor: rename networkextension target to AmneziaVPNNetworkExtension in macos build configuration * feat: add autostart support for Mac App Store builds on macOS Fixes: QA-8 * feat: add debug logging to Autostart functionality on macOS * Revert "feat: add autostart support for Mac App Store builds on macOS" This reverts commit 3bd25656fb4986d01e5bd6dd265f7279a73bd2a8. * feat: add platform-specific close window behavior for macOS App Store build with Network Extension Closes: QA-12 * When the application starts with "Start minimized" enabled on macOS (especially the sandboxed App-Store build compiled with MACOS_NE), fully hiding the window prevents it from being restored by clicking the Dock icon. The proper behaviour is to start the window in the *minimized* state instead. That way the window is still part of the window list and the system automatically brings it back when the user clicks the Dock icon, replicating the native experience. On the other platforms we keep the old behaviour (hide the window completely and rely on the tray icon), therefore we switch at runtime by checking the current OS. Closes: QA-7 Closes: QA-8 * Revert "When the application starts with "Start minimized" enabled on macOS (especially the" This reverts commit 7b0d17987cdfdbc4cedc3822bf3fd2e4973da452. * feat: MACOS_NE systray menu support * feat: add macOS notification handler and install event filter on main window * feat: implement custom close behavior for Amnezia application on different platforms * fix: update provisioning profile specifiers for macos builds * fix: Fatal error in logs CLI-216 * fix: disabled unavailable on macos ne service logs * fix: dock icon now hides only when window is closed; menubar icon shows always Initial state of the docker icon to be presented follows "Start minimized" setting in app settings. * temp-fix: temporary disable all OpenVPN options of VPN on MACOS_NE since it's not working yet. * fix: build script updated * feat: add macOS NE build workflow to GitHub Actions * fix: Not working Auto start toggle is hidden * fix: Log spamming during xray connection fixed * 3rd-prebuild points to commit that stores macos_ne universal binaries. * fix: missing native dependency on linking stage fixed * chore: update link to submodule --------- Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
361 lines
9.7 KiB
C++
Executable File
361 lines
9.7 KiB
C++
Executable File
#include <QCoreApplication>
|
|
#include <QDebug>
|
|
#include <QDir>
|
|
#include <QProcess>
|
|
#include <QRandomGenerator>
|
|
#include <QRegularExpression>
|
|
#include <QStandardPaths>
|
|
#include <QUrl>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
|
|
#include "utilities.h"
|
|
|
|
#ifdef Q_OS_WINDOWS
|
|
QString printErrorMessage(DWORD errorCode) {
|
|
LPVOID lpMsgBuf;
|
|
|
|
DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS;
|
|
|
|
DWORD dwLanguageId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
|
|
|
|
FormatMessageW(
|
|
dwFlags,
|
|
NULL,
|
|
errorCode,
|
|
dwLanguageId,
|
|
(LPWSTR)&lpMsgBuf,
|
|
0,
|
|
NULL
|
|
);
|
|
|
|
QString errorMsg = QString::fromWCharArray((LPCWSTR)lpMsgBuf);
|
|
LocalFree(lpMsgBuf);
|
|
return errorMsg.trimmed();
|
|
}
|
|
|
|
QString Utils::getNextDriverLetter()
|
|
{
|
|
DWORD drivesBitmask = GetLogicalDrives();
|
|
if (drivesBitmask == 0) {
|
|
DWORD error = GetLastError();
|
|
qDebug() << "GetLogicalDrives failed. Error code:" << error;
|
|
return "";
|
|
}
|
|
|
|
QString letters = "FGHIJKLMNOPQRSTUVWXYZ";
|
|
QString availableLetter;
|
|
|
|
for (int i = letters.size() - 1; i >= 0; --i) {
|
|
QChar letterChar = letters.at(i);
|
|
int driveIndex = letterChar.toLatin1() - 'A';
|
|
|
|
if ((drivesBitmask & (1 << driveIndex)) == 0) {
|
|
availableLetter = letterChar;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (availableLetter.isEmpty()) {
|
|
qDebug() << "Can't find free drive letter";
|
|
return "";
|
|
}
|
|
|
|
return availableLetter;
|
|
}
|
|
#endif
|
|
|
|
QString Utils::getRandomString(int len)
|
|
{
|
|
const QString possibleCharacters = QStringLiteral("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
|
QString randomString;
|
|
|
|
for (int i = 0; i < len; ++i) {
|
|
randomString.append(possibleCharacters.at(QRandomGenerator::system()->bounded(possibleCharacters.length())));
|
|
}
|
|
|
|
return randomString;
|
|
}
|
|
|
|
QString Utils::VerifyJsonString(const QString &source)
|
|
{
|
|
QJsonParseError error;
|
|
QJsonDocument doc = QJsonDocument::fromJson(source.toUtf8(), &error);
|
|
Q_UNUSED(doc)
|
|
|
|
if (error.error == QJsonParseError::NoError) {
|
|
return "";
|
|
} else {
|
|
qDebug() << "WARNING: Json parse returns: " + error.errorString();
|
|
return error.errorString();
|
|
}
|
|
}
|
|
|
|
QJsonObject Utils::JsonFromString(const QString &string)
|
|
{
|
|
auto removeComment = string.trimmed();
|
|
if (removeComment != string.trimmed()) {
|
|
qDebug() << "Some comments have been removed from the json.";
|
|
}
|
|
QJsonDocument doc = QJsonDocument::fromJson(removeComment.toUtf8());
|
|
return doc.object();
|
|
}
|
|
|
|
QString Utils::SafeBase64Decode(QString string)
|
|
{
|
|
QByteArray ba = string.replace(QChar('-'), QChar('+')).replace(QChar('_'), QChar('/')).toUtf8();
|
|
return QByteArray::fromBase64(ba, QByteArray::Base64Option::OmitTrailingEquals);
|
|
}
|
|
|
|
QString Utils::JsonToString(const QJsonObject &json, QJsonDocument::JsonFormat format)
|
|
{
|
|
QJsonDocument doc;
|
|
doc.setObject(json);
|
|
return doc.toJson(format);
|
|
}
|
|
|
|
QString Utils::JsonToString(const QJsonArray &array, QJsonDocument::JsonFormat format)
|
|
{
|
|
QJsonDocument doc;
|
|
doc.setArray(array);
|
|
return doc.toJson(format);
|
|
}
|
|
|
|
bool Utils::initializePath(const QString &path)
|
|
{
|
|
QDir dir;
|
|
if (!dir.mkpath(path)) {
|
|
qWarning().noquote() << QString("Cannot initialize path: '%1'").arg(path);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Utils::createEmptyFile(const QString &path)
|
|
{
|
|
QFile f(path);
|
|
return f.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
|
}
|
|
|
|
QString Utils::executable(const QString &baseName, bool absPath)
|
|
{
|
|
QString ext;
|
|
#ifdef Q_OS_WIN
|
|
ext = ".exe";
|
|
#endif
|
|
const QString fileName = baseName + ext;
|
|
if (!absPath) {
|
|
return fileName;
|
|
}
|
|
return QCoreApplication::applicationDirPath() + "/" + fileName;
|
|
}
|
|
|
|
QString Utils::usrExecutable(const QString &baseName)
|
|
{
|
|
if (QFileInfo::exists("/usr/sbin/" + baseName))
|
|
return ("/usr/sbin/" + baseName);
|
|
else
|
|
return ("/usr/bin/" + baseName);
|
|
}
|
|
|
|
bool Utils::processIsRunning(const QString &fileName, const bool fullFlag)
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
if (hSnapshot == INVALID_HANDLE_VALUE) {
|
|
qWarning() << "Utils::processIsRunning error CreateToolhelp32Snapshot";
|
|
return false;
|
|
}
|
|
|
|
PROCESSENTRY32W pe32;
|
|
pe32.dwSize = sizeof(PROCESSENTRY32W);
|
|
|
|
if (!Process32FirstW(hSnapshot, &pe32)) {
|
|
CloseHandle(hSnapshot);
|
|
qWarning() << "Utils::processIsRunning error Process32FirstW";
|
|
return false;
|
|
}
|
|
|
|
do {
|
|
QString exeFile = QString::fromWCharArray(pe32.szExeFile);
|
|
|
|
if (exeFile.compare(fileName, Qt::CaseInsensitive) == 0) {
|
|
CloseHandle(hSnapshot);
|
|
return true;
|
|
}
|
|
} while (Process32NextW(hSnapshot, &pe32));
|
|
|
|
CloseHandle(hSnapshot);
|
|
return false;
|
|
|
|
#elif defined(Q_OS_IOS) || defined(Q_OS_ANDROID) || defined(MACOS_NE)
|
|
return false;
|
|
#else
|
|
QProcess process;
|
|
QStringList arguments;
|
|
if (fullFlag) {
|
|
arguments << "-f";
|
|
}
|
|
arguments << fileName;
|
|
process.setProcessChannelMode(QProcess::MergedChannels);
|
|
process.start("pgrep", arguments);
|
|
process.waitForFinished();
|
|
if (process.exitStatus() == QProcess::NormalExit) {
|
|
if (fullFlag) {
|
|
return (process.readLine().toUInt() > 0);
|
|
} else {
|
|
return (process.readAll().toUInt() > 0);
|
|
}
|
|
}
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool Utils::killProcessByName(const QString &name)
|
|
{
|
|
qDebug().noquote() << "Kill process" << name;
|
|
#ifdef Q_OS_WIN
|
|
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
if (hSnapshot == INVALID_HANDLE_VALUE)
|
|
return false;
|
|
|
|
PROCESSENTRY32W pe32;
|
|
pe32.dwSize = sizeof(PROCESSENTRY32W);
|
|
|
|
bool success = false;
|
|
|
|
if (Process32FirstW(hSnapshot, &pe32)) {
|
|
do {
|
|
QString exeFile = QString::fromWCharArray(pe32.szExeFile);
|
|
|
|
if (exeFile.compare(name, Qt::CaseInsensitive) == 0) {
|
|
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID);
|
|
if (hProcess != NULL) {
|
|
if (TerminateProcess(hProcess, 0)) {
|
|
success = true;
|
|
} else {
|
|
DWORD error = GetLastError();
|
|
qCritical() << "Can't terminate process" << exeFile << "(PID:" << pe32.th32ProcessID << "). Error:" << printErrorMessage(error);
|
|
}
|
|
CloseHandle(hProcess);
|
|
} else {
|
|
DWORD error = GetLastError();
|
|
qCritical() << "Can't open process for termination" << exeFile << "(PID:" << pe32.th32ProcessID << "). Error:" << printErrorMessage(error);
|
|
}
|
|
}
|
|
} while (Process32NextW(hSnapshot, &pe32));
|
|
}
|
|
|
|
CloseHandle(hSnapshot);
|
|
return success;
|
|
#elif defined Q_OS_IOS || defined(Q_OS_ANDROID)
|
|
return false;
|
|
#else
|
|
return QProcess::execute("pkill", { name }) == 0;
|
|
#endif
|
|
}
|
|
|
|
QString Utils::openVpnExecPath()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
return Utils::executable("openvpn/openvpn", true);
|
|
#elif defined Q_OS_LINUX
|
|
// We have service that runs OpenVPN on Linux. We need to make same
|
|
// path for client and service.
|
|
return Utils::executable("../../client/bin/openvpn", true);
|
|
#else
|
|
return Utils::executable("/openvpn", true);
|
|
#endif
|
|
}
|
|
|
|
QString Utils::wireguardExecPath()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
return Utils::executable("wireguard/wireguard-service", true);
|
|
#elif defined Q_OS_LINUX
|
|
return Utils::usrExecutable("wg-quick");
|
|
#elif defined Q_OS_MAC
|
|
return Utils::executable("/wireguard", true);
|
|
#else
|
|
return {};
|
|
#endif
|
|
}
|
|
|
|
QString Utils::certUtilPath()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
QString winPath = QString::fromUtf8(qgetenv("windir"));
|
|
return winPath + "\\system32\\certutil.exe";
|
|
#else
|
|
return "";
|
|
#endif
|
|
}
|
|
|
|
QString Utils::tun2socksPath()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
return Utils::executable("xray/tun2socks", true);
|
|
#elif defined Q_OS_LINUX
|
|
// We have service that runs OpenVPN on Linux. We need to make same
|
|
// path for client and service.
|
|
return Utils::executable("../../client/bin/tun2socks", true);
|
|
#else
|
|
return Utils::executable("/tun2socks", true);
|
|
#endif
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
// Inspired from http://stackoverflow.com/a/15281070/1529139
|
|
// and http://stackoverflow.com/q/40059902/1529139
|
|
bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent)
|
|
{
|
|
bool success = false;
|
|
DWORD thisConsoleId = GetCurrentProcessId();
|
|
// Leave current console if it exists
|
|
// (otherwise AttachConsole will return ERROR_ACCESS_DENIED)
|
|
bool consoleDetached = (FreeConsole() != FALSE);
|
|
|
|
if (AttachConsole(dwProcessId) != FALSE) {
|
|
// Add a fake Ctrl-C handler for avoid instant kill is this console
|
|
// WARNING: do not revert it or current program will be also killed
|
|
SetConsoleCtrlHandler(nullptr, true);
|
|
success = (GenerateConsoleCtrlEvent(dwCtrlEvent, 0) != FALSE);
|
|
FreeConsole();
|
|
}
|
|
|
|
if (consoleDetached) {
|
|
// Create a new console if previous was deleted by OS
|
|
if (AttachConsole(thisConsoleId) == FALSE) {
|
|
int errorCode = GetLastError();
|
|
if (errorCode == 31) // 31=ERROR_GEN_FAILURE
|
|
{
|
|
AllocConsole();
|
|
}
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
#endif
|
|
|
|
void Utils::logException(const std::exception &e)
|
|
{
|
|
qCritical() << e.what();
|
|
try {
|
|
std::rethrow_if_nested(e);
|
|
} catch (const std::exception &nested) {
|
|
logException(nested);
|
|
} catch (...) {}
|
|
}
|
|
|
|
void Utils::logException(const std::exception_ptr &eptr)
|
|
{
|
|
try {
|
|
if (eptr) std::rethrow_exception(eptr);
|
|
} catch (const std::exception &e) {
|
|
logException(e);
|
|
} catch (...) {}
|
|
}
|