mirror of
https://github.com/MatsuriDayo/nekoray.git
synced 2025-12-18 21:14:37 +03:00
feat: activate window when multiple open
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
1
3rdparty/QThreadCreateThread.hpp
vendored
1
3rdparty/QThreadCreateThread.hpp
vendored
@@ -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
25
3rdparty/RunGuard.hpp
vendored
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
15
sys/windows/guihelper.cpp
Normal 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
5
sys/windows/guihelper.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class QWidget;
|
||||||
|
|
||||||
|
void Windows_QWidget_SetForegroundWindow(QWidget* w);
|
||||||
7
test/test-qt512-sdk-build.sh
Normal file
7
test/test-qt512-sdk-build.sh
Normal 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
|
||||||
@@ -1333,10 +1333,6 @@ As of January 1, 2022, compatibility with MD5 authentication information will be
|
|||||||
<source>This profile is cleartext, don't use it if the server is not in your local network.</source>
|
<source>This profile is cleartext, don'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>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user