feat: activate window when multiple open

This commit is contained in:
arm64v8a
2022-11-27 18:24:21 +09:00
parent db6569ffb0
commit 69390afb9a
14 changed files with 120 additions and 35 deletions

6
.gitignore vendored
View File

@@ -70,8 +70,8 @@ Thumbs.db
*.exe *.exe
# Custom # Custom
/nekoray/ /nekoray
/build/ /build
CMakeLists.txt.user* CMakeLists.txt.user*
/cmake-build-* /cmake-build-*
/build-* /build-*
@@ -81,4 +81,4 @@ CMakeLists.txt.user*
# Deploy # Deploy
/deployment /deployment
/neko*.sh /neko*.sh
/qtsdk/ /qtsdk

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <future>
#include <QThread> #include <QThread>
// FOR OLD QT // FOR OLD QT

25
3rdparty/RunGuard.hpp vendored
View File

@@ -7,15 +7,14 @@
#include <QCryptographicHash> #include <QCryptographicHash>
class RunGuard { class RunGuard {
public: public:
RunGuard(const QString &key); RunGuard(const QString &key);
~RunGuard(); ~RunGuard();
bool isAnotherRunning(); bool isAnotherRunning(quint64 *data_out);
bool tryToRun(); bool tryToRun(quint64 *data_in);
void release(); void release();
@@ -42,12 +41,10 @@ namespace {
return data; return data;
} }
} } // namespace
RunGuard::RunGuard(const QString &key) RunGuard::RunGuard(const QString &key)
: key(key), memLockKey(generateKeyHash(key, "_memLockKey")), : key(key), memLockKey(generateKeyHash(key, "_memLockKey")), sharedmemKey(generateKeyHash(key, "_sharedmemKey")), sharedMem(sharedmemKey), memLock(memLockKey, 1) {
sharedmemKey(generateKeyHash(key, "_sharedmemKey")), sharedMem(sharedmemKey), memLock(memLockKey, 1) {
memLock.acquire(); memLock.acquire();
{ {
QSharedMemory fix(sharedmemKey); // Fix for *nix: http://habrahabr.ru/post/173281/ QSharedMemory fix(sharedmemKey); // Fix for *nix: http://habrahabr.ru/post/173281/
@@ -60,26 +57,32 @@ RunGuard::~RunGuard() {
release(); release();
} }
bool RunGuard::isAnotherRunning() { bool RunGuard::isAnotherRunning(quint64 *data_out) {
if (sharedMem.isAttached()) if (sharedMem.isAttached())
return false; return false;
memLock.acquire(); memLock.acquire();
const bool isRunning = sharedMem.attach(); const bool isRunning = sharedMem.attach();
if (isRunning) if (isRunning) {
if (data_out != nullptr) {
memcpy(data_out, sharedMem.data(), sizeof(quint64));
}
sharedMem.detach(); sharedMem.detach();
}
memLock.release(); memLock.release();
return isRunning; return isRunning;
} }
bool RunGuard::tryToRun() { bool RunGuard::tryToRun(quint64 *data_in) {
if (isAnotherRunning()) // Extra check if (isAnotherRunning(nullptr)) // Extra check
return false; return false;
memLock.acquire(); memLock.acquire();
const bool result = sharedMem.create(sizeof(quint64)); const bool result = sharedMem.create(sizeof(quint64));
if (result) memcpy(sharedMem.data(), data_in, sizeof(quint64));
memLock.release(); memLock.release();
if (!result) { if (!result) {
release(); release();
return false; return false;

View File

@@ -1,5 +1,5 @@
if (WIN32) 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) set(PLATFORM_FUCKING_LIBRARIES wininet wsock32 ws2_32 user32 rasapi32 iphlpapi)
include(cmake/fuck_windows/generate_product_version.cmake) include(cmake/fuck_windows/generate_product_version.cmake)

View File

@@ -1,5 +1,9 @@
#pragma once #pragma once
#ifdef Q_OS_WIN
#include "sys/windows/guihelper.h"
#endif
// Dialogs // Dialogs
#define Dialog_DialogBasicSettings "DialogBasicSettings" #define Dialog_DialogBasicSettings "DialogBasicSettings"
@@ -58,3 +62,21 @@
CACHE.a = QJsonObject2QString(result, true); \ CACHE.a = QJsonObject2QString(result, true); \
if (result.isEmpty()) CACHE.a = ""; \ if (result.isEmpty()) CACHE.a = ""; \
editor->deleteLater(); 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

View File

@@ -69,6 +69,13 @@ QString GetRandomString(int randomStringLength) {
return randomString; return randomString;
} }
quint64 GetRandomUint64() {
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<quint64> dist;
return dist(mt);
}
// QString >> QJson // QString >> QJson
QJsonObject QString2QJsonObject(const QString &jsonString) { QJsonObject QString2QJsonObject(const QString &jsonString) {
QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonString.toUtf8()); QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonString.toUtf8());

View File

@@ -55,6 +55,8 @@ QString GetQueryValue(const QUrlQuery &q, const QString &key, const QString &def
QString GetRandomString(int randomStringLength); QString GetRandomString(int randomStringLength);
quint64 GetRandomUint64();
inline QString UrlSafe_encode(const QString &s) { inline QString UrlSafe_encode(const QString &s) {
return s.toUtf8().toPercentEncoding().replace(" ", "%20"); return s.toUtf8().toPercentEncoding().replace(" ", "%20");
} }

View File

@@ -7,6 +7,8 @@
#include <QTranslator> #include <QTranslator>
#include <QMessageBox> #include <QMessageBox>
#include <QStandardPaths> #include <QStandardPaths>
#include <QLocalSocket>
#include <QLocalServer>
#include "3rdparty/RunGuard.hpp" #include "3rdparty/RunGuard.hpp"
#include "main/NekoRay.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[]) { int main(int argc, char *argv[]) {
// Core dump // Core dump
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
@@ -72,11 +76,25 @@ int main(int argc, char *argv[]) {
// RunGuard // RunGuard
RunGuard guard("nekoray" + wd.absolutePath()); RunGuard guard("nekoray" + wd.absolutePath());
if (!NekoRay::dataStore->flag_many) { quint64 guard_data_in = GetRandomUint64();
if (!guard.tryToRun()) { quint64 guard_data_out = 0;
QMessageBox::warning(nullptr, "NekoRay", QObject::tr("Another program is running.")); 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; 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(); }; MF_release_runguard = [&] { guard.release(); };
@@ -159,6 +177,19 @@ int main(int argc, char *argv[]) {
signal(SIGTERM, signal_handler); signal(SIGTERM, signal_handler);
signal(SIGINT, 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(); UI_InitMainWindow();
return QApplication::exec(); return QApplication::exec();
} }

15
sys/windows/guihelper.cpp Normal file
View File

@@ -0,0 +1,15 @@
#include "guihelper.h"
#include <QWidget>
#include <windows.h>
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);
}

5
sys/windows/guihelper.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
class QWidget;
void Windows_QWidget_SetForegroundWindow(QWidget* w);

View File

@@ -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

View File

@@ -1333,10 +1333,6 @@ As of January 1, 2022, compatibility with MD5 authentication information will be
<source>This profile is cleartext, don&apos;t use it if the server is not in your local network.</source> <source>This profile is cleartext, don&apos;t use it if the server is not in your local network.</source>
<translation>使</translation> <translation>使</translation>
</message> </message>
<message>
<source>Another program is running.</source>
<translation> Nekoray </translation>
</message>
<message> <message>
<source>Select</source> <source>Select</source>
<translation></translation> <translation></translation>

View File

@@ -22,19 +22,13 @@
#include "qv2ray/v2/ui/LogHighlighter.hpp" #include "qv2ray/v2/ui/LogHighlighter.hpp"
#ifndef NKR_NO_EXTERNAL #ifndef NKR_NO_EXTERNAL
#include "3rdparty/ZxingQtReader.hpp" #include "3rdparty/ZxingQtReader.hpp"
#endif #endif
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include "3rdparty/WinCommander.hpp" #include "3rdparty/WinCommander.hpp"
#else #else
#include <unistd.h> #include <unistd.h>
#endif #endif
#include <QClipboard> #include <QClipboard>
@@ -238,9 +232,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
if (this->isVisible()) { if (this->isVisible()) {
hide(); hide();
} else { } else {
this->showNormal(); ACTIVE_THIS_WINDOW
this->raise();
this->activateWindow();
} }
break; break;
default: default:
@@ -445,6 +437,7 @@ void MainWindow::show_group(int gid) {
// callback // callback
void MainWindow::dialog_message_impl(const QString &sender, const QString &info) { void MainWindow::dialog_message_impl(const QString &sender, const QString &info) {
// info
if (info.contains("UpdateIcon")) { if (info.contains("UpdateIcon")) {
icon_status = -1; icon_status = -1;
refresh_status(); refresh_status();
@@ -464,8 +457,10 @@ void MainWindow::dialog_message_impl(const QString &sender, const QString &info)
if (info == "RestartProgram") { if (info == "RestartProgram") {
this->exit_reason = 2; this->exit_reason = 2;
on_menu_exit_triggered(); on_menu_exit_triggered();
} else if (info == "Raise") {
ACTIVE_THIS_WINDOW
} }
// // sender
if (sender == Dialog_DialogEditProfile) { if (sender == Dialog_DialogEditProfile) {
if (info == "accept") { if (info == "accept") {
refresh_proxy_list(); refresh_proxy_list();

View File

@@ -310,19 +310,20 @@ void MainWindow::CheckUpdate() {
} }
runOnUiThread([=] { runOnUiThread([=] {
auto allow_updater = !NekoRay::dataStore->flag_use_appdata;
auto note_pre_release = response.is_pre_release() ? " (Pre-release)" : ""; auto note_pre_release = response.is_pre_release() ? " (Pre-release)" : "";
QMessageBox box(QMessageBox::Question, QObject::tr("Update") + note_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())); QObject::tr("Update found: %1\nRelease note:\n%2").arg(response.assets_name().c_str(), response.release_note().c_str()));
// //
QAbstractButton *btn1 = nullptr; QAbstractButton *btn1 = nullptr;
if (!NekoRay::dataStore->flag_use_appdata) { if (allow_updater) {
btn1 = box.addButton(QObject::tr("Update"), QMessageBox::AcceptRole); btn1 = box.addButton(QObject::tr("Update"), QMessageBox::AcceptRole);
} }
QAbstractButton *btn2 = box.addButton(QObject::tr("Open in browser"), QMessageBox::AcceptRole); QAbstractButton *btn2 = box.addButton(QObject::tr("Open in browser"), QMessageBox::AcceptRole);
box.addButton(QObject::tr("Close"), QMessageBox::RejectRole); box.addButton(QObject::tr("Close"), QMessageBox::RejectRole);
box.exec(); box.exec();
// //
if (btn1 == box.clickedButton()) { if (btn1 == box.clickedButton() && allow_updater) {
// Download Update // Download Update
runOnNewThread([=] { runOnNewThread([=] {
bool ok2; bool ok2;