diff --git a/.gitignore b/.gitignore index 587b7e9..e0a619e 100644 --- a/.gitignore +++ b/.gitignore @@ -70,8 +70,8 @@ Thumbs.db *.exe # Custom -/nekoray/ -/build/ +/nekoray +/build CMakeLists.txt.user* /cmake-build-* /build-* @@ -81,4 +81,4 @@ CMakeLists.txt.user* # Deploy /deployment /neko*.sh -/qtsdk/ +/qtsdk diff --git a/3rdparty/QThreadCreateThread.hpp b/3rdparty/QThreadCreateThread.hpp index 2822ba8..ba6fe5f 100644 --- a/3rdparty/QThreadCreateThread.hpp +++ b/3rdparty/QThreadCreateThread.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include // FOR OLD QT diff --git a/3rdparty/RunGuard.hpp b/3rdparty/RunGuard.hpp index 3702610..f24c798 100644 --- a/3rdparty/RunGuard.hpp +++ b/3rdparty/RunGuard.hpp @@ -7,15 +7,14 @@ #include class RunGuard { - public: RunGuard(const QString &key); ~RunGuard(); - bool isAnotherRunning(); + bool isAnotherRunning(quint64 *data_out); - bool tryToRun(); + bool tryToRun(quint64 *data_in); void release(); @@ -42,15 +41,13 @@ namespace { return data; } -} - +} // namespace RunGuard::RunGuard(const QString &key) - : key(key), memLockKey(generateKeyHash(key, "_memLockKey")), - sharedmemKey(generateKeyHash(key, "_sharedmemKey")), sharedMem(sharedmemKey), memLock(memLockKey, 1) { + : key(key), memLockKey(generateKeyHash(key, "_memLockKey")), sharedmemKey(generateKeyHash(key, "_sharedmemKey")), sharedMem(sharedmemKey), memLock(memLockKey, 1) { memLock.acquire(); { - QSharedMemory fix(sharedmemKey); // Fix for *nix: http://habrahabr.ru/post/173281/ + QSharedMemory fix(sharedmemKey); // Fix for *nix: http://habrahabr.ru/post/173281/ fix.attach(); } memLock.release(); @@ -60,26 +57,32 @@ RunGuard::~RunGuard() { release(); } -bool RunGuard::isAnotherRunning() { +bool RunGuard::isAnotherRunning(quint64 *data_out) { if (sharedMem.isAttached()) return false; memLock.acquire(); const bool isRunning = sharedMem.attach(); - if (isRunning) + if (isRunning) { + if (data_out != nullptr) { + memcpy(data_out, sharedMem.data(), sizeof(quint64)); + } sharedMem.detach(); + } memLock.release(); return isRunning; } -bool RunGuard::tryToRun() { - if (isAnotherRunning()) // Extra check +bool RunGuard::tryToRun(quint64 *data_in) { + if (isAnotherRunning(nullptr)) // Extra check return false; memLock.acquire(); const bool result = sharedMem.create(sizeof(quint64)); + if (result) memcpy(sharedMem.data(), data_in, sizeof(quint64)); memLock.release(); + if (!result) { release(); return false; diff --git a/cmake/fuck_windows/fuck.cmake b/cmake/fuck_windows/fuck.cmake index ed82fcd..c91e82a 100644 --- a/cmake/fuck_windows/fuck.cmake +++ b/cmake/fuck_windows/fuck.cmake @@ -1,5 +1,5 @@ if (WIN32) - set(PLATFORM_FUCKING_SOURCES 3rdparty/WinCommander.cpp) + set(PLATFORM_FUCKING_SOURCES 3rdparty/WinCommander.cpp sys/windows/guihelper.cpp) set(PLATFORM_FUCKING_LIBRARIES wininet wsock32 ws2_32 user32 rasapi32 iphlpapi) include(cmake/fuck_windows/generate_product_version.cmake) diff --git a/main/GuiUtils.hpp b/main/GuiUtils.hpp index fdf13f6..6573226 100644 --- a/main/GuiUtils.hpp +++ b/main/GuiUtils.hpp @@ -1,5 +1,9 @@ #pragma once +#ifdef Q_OS_WIN +#include "sys/windows/guihelper.h" +#endif + // Dialogs #define Dialog_DialogBasicSettings "DialogBasicSettings" @@ -58,3 +62,21 @@ CACHE.a = QJsonObject2QString(result, true); \ if (result.isEmpty()) CACHE.a = ""; \ editor->deleteLater(); + +// System + +#define _ACTIVE_THIS_WINDOW_COMMON \ + hide(); \ + showMinimized(); \ + showNormal(); \ + activateWindow(); \ + setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); \ + raise(); + +#ifdef Q_OS_WIN +#define ACTIVE_THIS_WINDOW \ + _ACTIVE_THIS_WINDOW_COMMON \ + Windows_QWidget_SetForegroundWindow(this); +#else +#define ACTIVE_THIS_WINDOW _ACTIVE_THIS_WINDOW_COMMON +#endif diff --git a/main/NekoRay_Utils.cpp b/main/NekoRay_Utils.cpp index 08e457d..4fcb08e 100644 --- a/main/NekoRay_Utils.cpp +++ b/main/NekoRay_Utils.cpp @@ -69,6 +69,13 @@ QString GetRandomString(int randomStringLength) { return randomString; } +quint64 GetRandomUint64() { + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution dist; + return dist(mt); +} + // QString >> QJson QJsonObject QString2QJsonObject(const QString &jsonString) { QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonString.toUtf8()); diff --git a/main/NekoRay_Utils.hpp b/main/NekoRay_Utils.hpp index 7fe01e1..1e76744 100644 --- a/main/NekoRay_Utils.hpp +++ b/main/NekoRay_Utils.hpp @@ -55,6 +55,8 @@ QString GetQueryValue(const QUrlQuery &q, const QString &key, const QString &def QString GetRandomString(int randomStringLength); +quint64 GetRandomUint64(); + inline QString UrlSafe_encode(const QString &s) { return s.toUtf8().toPercentEncoding().replace(" ", "%20"); } diff --git a/main/main.cpp b/main/main.cpp index 97d6e99..6b4239f 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "3rdparty/RunGuard.hpp" #include "main/NekoRay.hpp" @@ -22,6 +24,8 @@ void signal_handler(int signum) { } } +#define LOCAL_SERVER_PREFIX "nekoraylocalserver-" + int main(int argc, char *argv[]) { // Core dump #ifdef Q_OS_WIN @@ -72,11 +76,25 @@ int main(int argc, char *argv[]) { // RunGuard RunGuard guard("nekoray" + wd.absolutePath()); - if (!NekoRay::dataStore->flag_many) { - if (!guard.tryToRun()) { - QMessageBox::warning(nullptr, "NekoRay", QObject::tr("Another program is running.")); + quint64 guard_data_in = GetRandomUint64(); + quint64 guard_data_out = 0; + if (!NekoRay::dataStore->flag_many && !guard.tryToRun(&guard_data_in)) { + // Some Good System + if (guard.isAnotherRunning(&guard_data_out)) { + // Wake up a running instance + QLocalSocket socket; + socket.connectToServer(LOCAL_SERVER_PREFIX + Int2String(guard_data_out)); + qDebug() << socket.fullServerName(); + if (!socket.waitForConnected(500)) { + qDebug() << "Failed to wake a running instance."; + return 0; + } + qDebug() << "connected to local server, try to raise another program"; return 0; } + // Some Bad System + QMessageBox::warning(nullptr, "NekoRay", "RunGuard disallow to run, use -many to force start."); + return 0; } MF_release_runguard = [&] { guard.release(); }; @@ -159,6 +177,19 @@ int main(int argc, char *argv[]) { signal(SIGTERM, signal_handler); signal(SIGINT, signal_handler); + // QLocalServer + QLocalServer server; + auto server_name = LOCAL_SERVER_PREFIX + Int2String(guard_data_in); + QLocalServer::removeServer(server_name); + server.listen(server_name); + QObject::connect(&server, &QLocalServer::newConnection, &a, [&] { + auto socket = server.nextPendingConnection(); + qDebug() << "nextPendingConnection:" << server_name << socket; + socket->deleteLater(); + // raise main window + MW_dialog_message("", "Raise"); + }); + UI_InitMainWindow(); return QApplication::exec(); } diff --git a/sys/windows/guihelper.cpp b/sys/windows/guihelper.cpp new file mode 100644 index 0000000..a297415 --- /dev/null +++ b/sys/windows/guihelper.cpp @@ -0,0 +1,15 @@ + +#include "guihelper.h" + +#include + +#include + +void Windows_QWidget_SetForegroundWindow(QWidget* w) { + HWND hForgroundWnd = GetForegroundWindow(); + DWORD dwForeID = ::GetWindowThreadProcessId(hForgroundWnd, NULL); + DWORD dwCurID = ::GetCurrentThreadId(); + ::AttachThreadInput(dwCurID, dwForeID, TRUE); + ::SetForegroundWindow((HWND) w->winId()); + ::AttachThreadInput(dwCurID, dwForeID, FALSE); +} diff --git a/sys/windows/guihelper.h b/sys/windows/guihelper.h new file mode 100644 index 0000000..d7afb91 --- /dev/null +++ b/sys/windows/guihelper.h @@ -0,0 +1,5 @@ +#pragma once + +class QWidget; + +void Windows_QWidget_SetForegroundWindow(QWidget* w); diff --git a/test/test-qt512-sdk-build.sh b/test/test-qt512-sdk-build.sh new file mode 100644 index 0000000..9414691 --- /dev/null +++ b/test/test-qt512-sdk-build.sh @@ -0,0 +1,7 @@ +QT=$PWD/qtsdk/5.12.12/gcc_64 +BUILD=build-sdk-qt512 +rm -rf $BUILD +mkdir -p $BUILD +cd $BUILD +cmake -GNinja -DCMAKE_PREFIX_PATH=$QT .. +ninja diff --git a/translations/zh_CN.ts b/translations/zh_CN.ts index cf464f6..6349926 100644 --- a/translations/zh_CN.ts +++ b/translations/zh_CN.ts @@ -1333,10 +1333,6 @@ As of January 1, 2022, compatibility with MD5 authentication information will be This profile is cleartext, don't use it if the server is not in your local network. 该配置为明文传输,如果服务器不在本地局域网,请不要使用。 - - Another program is running. - 另一个 Nekoray 实例正在运行。 - Select 选择 diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 5c4617e..337e5ba 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -22,19 +22,13 @@ #include "qv2ray/v2/ui/LogHighlighter.hpp" #ifndef NKR_NO_EXTERNAL - #include "3rdparty/ZxingQtReader.hpp" - #endif #ifdef Q_OS_WIN - #include "3rdparty/WinCommander.hpp" - #else - #include - #endif #include @@ -238,9 +232,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi if (this->isVisible()) { hide(); } else { - this->showNormal(); - this->raise(); - this->activateWindow(); + ACTIVE_THIS_WINDOW } break; default: @@ -445,6 +437,7 @@ void MainWindow::show_group(int gid) { // callback void MainWindow::dialog_message_impl(const QString &sender, const QString &info) { + // info if (info.contains("UpdateIcon")) { icon_status = -1; refresh_status(); @@ -464,8 +457,10 @@ void MainWindow::dialog_message_impl(const QString &sender, const QString &info) if (info == "RestartProgram") { this->exit_reason = 2; on_menu_exit_triggered(); + } else if (info == "Raise") { + ACTIVE_THIS_WINDOW } - // + // sender if (sender == Dialog_DialogEditProfile) { if (info == "accept") { refresh_proxy_list(); diff --git a/ui/mainwindow_grpc.cpp b/ui/mainwindow_grpc.cpp index 7140f0e..a6a8f7e 100644 --- a/ui/mainwindow_grpc.cpp +++ b/ui/mainwindow_grpc.cpp @@ -310,19 +310,20 @@ void MainWindow::CheckUpdate() { } runOnUiThread([=] { + auto allow_updater = !NekoRay::dataStore->flag_use_appdata; auto note_pre_release = response.is_pre_release() ? " (Pre-release)" : ""; QMessageBox box(QMessageBox::Question, QObject::tr("Update") + note_pre_release, QObject::tr("Update found: %1\nRelease note:\n%2").arg(response.assets_name().c_str(), response.release_note().c_str())); // QAbstractButton *btn1 = nullptr; - if (!NekoRay::dataStore->flag_use_appdata) { + if (allow_updater) { btn1 = box.addButton(QObject::tr("Update"), QMessageBox::AcceptRole); } QAbstractButton *btn2 = box.addButton(QObject::tr("Open in browser"), QMessageBox::AcceptRole); box.addButton(QObject::tr("Close"), QMessageBox::RejectRole); box.exec(); // - if (btn1 == box.clickedButton()) { + if (btn1 == box.clickedButton() && allow_updater) { // Download Update runOnNewThread([=] { bool ok2;