diff --git a/CMakeLists.txt b/CMakeLists.txt
index 326e5794..8d44386e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -449,7 +449,7 @@ pkg_check_modules(GSTREAMER IMPORTED_TARGET gstreamer-sdp-1.0>=1.14 gstreamer-we
# single instance functionality
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
-add_subdirectory(third_party/SingleApplication-3.1.3.1/)
+add_subdirectory(third_party/SingleApplication-3.2.0-dc8042b/)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/README.md b/README.md
index 3e4cfa55..f24b8d13 100644
--- a/README.md
+++ b/README.md
@@ -42,7 +42,7 @@ Specifically there is support for:
- Basic communities support.
- Room switcher (ctrl-K).
- Light, Dark & System themes.
-- Creating separate profiles (command line only, use `--profile=name`).
+- Creating separate profiles (command line only, use `-p name`).
## Installation
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 674b5793..8ea44e13 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -21,6 +21,7 @@
#include <QByteArray>
#include <QCoreApplication>
+#include <QCryptographicHash>
#include <QFile>
#include <QHash>
#include <QMap>
@@ -41,6 +42,7 @@
#include "Logging.h"
#include "MatrixClient.h"
#include "Olm.h"
+#include "UserSettingsPage.h"
#include "Utils.h"
//! Should be changed when a breaking change occurs in the cache format.
@@ -165,17 +167,15 @@ Cache::Cache(const QString &userId, QObject *parent)
void
Cache::setup()
{
- nhlog::db()->debug("setting up cache");
+ UserSettings settings;
- auto statePath = QString("%1/%2")
- .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
- .arg(QString::fromUtf8(localUserId_.toUtf8().toHex()));
+ nhlog::db()->debug("setting up cache");
- cacheDirectory_ = QString("%1/%2")
+ cacheDirectory_ = QString("%1/%2%3")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
- .arg(QString::fromUtf8(localUserId_.toUtf8().toHex()));
+ .arg(QString::fromUtf8(localUserId_.toUtf8().toHex())).arg(QString::fromUtf8(settings.profile().toUtf8().toHex()));
- bool isInitial = !QFile::exists(statePath);
+ bool isInitial = !QFile::exists(cacheDirectory_);
env_ = lmdb::env::create();
env_.set_mapsize(DB_SIZE);
@@ -184,9 +184,9 @@ Cache::setup()
if (isInitial) {
nhlog::db()->info("initializing LMDB");
- if (!QDir().mkpath(statePath)) {
+ if (!QDir().mkpath(cacheDirectory_)) {
throw std::runtime_error(
- ("Unable to create state directory:" + statePath).toStdString().c_str());
+ ("Unable to create state directory:" + cacheDirectory_).toStdString().c_str());
}
}
@@ -194,7 +194,7 @@ Cache::setup()
// NOTE(Nico): We may want to use (MDB_MAPASYNC | MDB_WRITEMAP) in the future, but
// it can really mess up our database, so we shouldn't. For now, hopefully
// NOMETASYNC is fast enough.
- env_.open(statePath.toStdString().c_str(), MDB_NOMETASYNC | MDB_NOSYNC);
+ env_.open(cacheDirectory_.toStdString().c_str(), MDB_NOMETASYNC | MDB_NOSYNC);
} catch (const lmdb::error &e) {
if (e.code() != MDB_VERSION_MISMATCH && e.code() != MDB_INVALID) {
throw std::runtime_error("LMDB initialization failed" +
@@ -203,15 +203,14 @@ Cache::setup()
nhlog::db()->warn("resetting cache due to LMDB version mismatch: {}", e.what());
- QDir stateDir(statePath);
+ QDir stateDir(cacheDirectory_);
for (const auto &file : stateDir.entryList(QDir::NoDotAndDotDot)) {
if (!stateDir.remove(file))
throw std::runtime_error(
("Unable to delete file " + file).toStdString().c_str());
}
-
- env_.open(statePath.toStdString().c_str());
+ env_.open(cacheDirectory_.toStdString().c_str());
}
auto txn = lmdb::txn::begin(env_);
@@ -513,7 +512,6 @@ Cache::getLatestOlmSession(const std::string &curve25519)
auto db = getOlmSessionsDb(txn, curve25519);
std::string session_id, pickled_session;
- std::vector<std::string> res;
std::optional<StoredOlmSession> currentNewest;
@@ -577,10 +575,14 @@ Cache::restoreOlmAccount()
void
Cache::storeSecret(const std::string &name, const std::string &secret)
{
+ UserSettings settings;
QKeychain::WritePasswordJob job(QCoreApplication::applicationName());
job.setAutoDelete(false);
job.setInsecureFallback(true);
- job.setKey(QString::fromStdString(name));
+ job.setKey(
+ "matrix." +
+ QString(QCryptographicHash::hash(settings.profile().toUtf8(), QCryptographicHash::Sha256)) +
+ "." + name.c_str());
job.setTextData(QString::fromStdString(secret));
QEventLoop loop;
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
@@ -598,10 +600,14 @@ Cache::storeSecret(const std::string &name, const std::string &secret)
void
Cache::deleteSecret(const std::string &name)
{
+ UserSettings settings;
QKeychain::DeletePasswordJob job(QCoreApplication::applicationName());
job.setAutoDelete(false);
job.setInsecureFallback(true);
- job.setKey(QString::fromStdString(name));
+ job.setKey(
+ "matrix." +
+ QString(QCryptographicHash::hash(settings.profile().toUtf8(), QCryptographicHash::Sha256)) +
+ "." + name.c_str());
QEventLoop loop;
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
@@ -613,10 +619,14 @@ Cache::deleteSecret(const std::string &name)
std::optional<std::string>
Cache::secret(const std::string &name)
{
+ UserSettings settings;
QKeychain::ReadPasswordJob job(QCoreApplication::applicationName());
job.setAutoDelete(false);
job.setInsecureFallback(true);
- job.setKey(QString::fromStdString(name));
+ job.setKey(
+ "matrix." +
+ QString(QCryptographicHash::hash(settings.profile().toUtf8(), QCryptographicHash::Sha256)) +
+ "." + name.c_str());
QEventLoop loop;
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index d056aca6..ffd0e30b 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -55,9 +55,9 @@
MainWindow *MainWindow::instance_ = nullptr;
-MainWindow::MainWindow(const QString profile, QWidget *parent)
- : QMainWindow(parent)
- , profile_{profile}
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent),
+ userSettings_{QSharedPointer<UserSettings>{new UserSettings}}
{
setWindowTitle(0);
setObjectName("MainWindow");
@@ -70,8 +70,7 @@ MainWindow::MainWindow(const QString profile, QWidget *parent)
font.setStyleStrategy(QFont::PreferAntialias);
setFont(font);
- userSettings_ = QSharedPointer<UserSettings>(new UserSettings);
- trayIcon_ = new TrayIcon(":/logos/nheko.svg", this);
+ trayIcon_ = new TrayIcon(":/logos/nheko.svg", this);
welcome_page_ = new WelcomePage(this);
login_page_ = new LoginPage(this);
@@ -150,15 +149,13 @@ MainWindow::MainWindow(const QString profile, QWidget *parent)
chat_page_->showQuickSwitcher();
});
- QSettings settings;
-
trayIcon_->setVisible(userSettings_->tray());
if (hasActiveUser()) {
- QString token = settings.value("auth/access_token").toString();
- QString home_server = settings.value("auth/home_server").toString();
- QString user_id = settings.value("auth/user_id").toString();
- QString device_id = settings.value("auth/device_id").toString();
+ QString token = userSettings_->accessToken();
+ QString home_server = userSettings_->homeserver();
+ QString user_id = userSettings_->userId();
+ QString device_id = userSettings_->deviceId();
http::client()->set_access_token(token.toStdString());
http::client()->set_server(home_server.toStdString());
@@ -184,8 +181,9 @@ void
MainWindow::setWindowTitle(int notificationCount)
{
QString name = "nheko";
- if (!profile_.isEmpty())
- name += " | " + profile_;
+
+ if (!userSettings_.data()->profile().isEmpty())
+ name += " | " + userSettings_.data()->profile();
if (notificationCount > 0) {
name.append(QString{" (%1)"}.arg(notificationCount));
}
@@ -279,11 +277,10 @@ MainWindow::showChatPage()
std::to_string(http::client()->port()));
auto token = QString::fromStdString(http::client()->access_token());
- QSettings settings;
- settings.setValue("auth/access_token", token);
- settings.setValue("auth/home_server", homeserver);
- settings.setValue("auth/user_id", userid);
- settings.setValue("auth/device_id", device_id);
+ userSettings_.data()->setUserId(userid);
+ userSettings_.data()->setAccessToken(token);
+ userSettings_.data()->setDeviceId(device_id);
+ userSettings_.data()->setHomeserver(homeserver);
showOverlayProgressBar();
@@ -341,9 +338,13 @@ bool
MainWindow::hasActiveUser()
{
QSettings settings;
+ QString prefix;
+ if (userSettings_->profile() != "")
+ prefix = "profile/" + userSettings_->profile() + "/";
- return settings.contains("auth/access_token") && settings.contains("auth/home_server") &&
- settings.contains("auth/user_id");
+ return settings.contains(prefix + "auth/access_token") &&
+ settings.contains(prefix + "auth/home_server") &&
+ settings.contains(prefix + "auth/user_id");
}
void
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 2f9ff897..0915a849 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -62,7 +62,7 @@ class MainWindow : public QMainWindow
Q_OBJECT
public:
- explicit MainWindow(const QString name, QWidget *parent = nullptr);
+ explicit MainWindow(QWidget *parent = nullptr);
static MainWindow *instance() { return instance_; };
void saveCurrentWindowSize();
@@ -149,6 +149,4 @@ private:
LoadingIndicator *spinner_ = nullptr;
JdenticonInterface *jdenticonInteface_ = nullptr;
-
- QString profile_;
};
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 708fb7fd..55f666c1 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -89,6 +89,14 @@ UserSettings::load()
cameraResolution_ = settings.value("user/camera_resolution", QString()).toString();
cameraFrameRate_ = settings.value("user/camera_frame_rate", QString()).toString();
useStunServer_ = settings.value("user/use_stun_server", false).toBool();
+ profile_ = settings.value("user/currentProfile", "").toString();
+
+ QString prefix =
+ (profile_ != "" && profile_ != "default") ? "profile/" + profile_ + "/" : "";
+ accessToken_ = settings.value(prefix + "auth/access_token", "").toString();
+ homeserver_ = settings.value(prefix + "auth/home_server", "").toString();
+ userId_ = settings.value(prefix + "auth/user_id", "").toString();
+ deviceId_ = settings.value(prefix + "auth/device_id", "").toString();
applyTheme();
}
@@ -373,6 +381,56 @@ UserSettings::setCameraFrameRate(QString frameRate)
}
void
+UserSettings::setProfile(QString profile)
+{
+ if (profile == profile_)
+ return;
+ profile_ = profile;
+ emit profileChanged(profile_);
+ save();
+}
+
+void
+UserSettings::setUserId(QString userId)
+{
+ if (userId == userId_)
+ return;
+ userId_ = userId;
+ emit userIdChanged(userId_);
+ save();
+}
+
+void
+UserSettings::setAccessToken(QString accessToken)
+{
+ if (accessToken == accessToken_)
+ return;
+ accessToken_ = accessToken;
+ emit accessTokenChanged(accessToken_);
+ save();
+}
+
+void
+UserSettings::setDeviceId(QString deviceId)
+{
+ if (deviceId == deviceId_)
+ return;
+ deviceId_ = deviceId;
+ emit deviceIdChanged(deviceId_);
+ save();
+}
+
+void
+UserSettings::setHomeserver(QString homeserver)
+{
+ if (homeserver == homeserver_)
+ return;
+ homeserver_ = homeserver;
+ emit homeserverChanged(homeserver_);
+ save();
+}
+
+void
UserSettings::applyTheme()
{
QFile stylefile;
@@ -436,14 +494,14 @@ UserSettings::save()
settings.beginGroup("window");
settings.setValue("tray", tray_);
settings.setValue("start_in_tray", startInTray_);
- settings.endGroup();
+ settings.endGroup(); // window
settings.beginGroup("timeline");
settings.setValue("buttons", buttonsInTimeline_);
settings.setValue("message_hover_highlight", messageHoverHighlight_);
settings.setValue("enlarge_emoji_only_msg", enlargeEmojiOnlyMessages_);
settings.setValue("max_width", timelineMaxWidth_);
- settings.endGroup();
+ settings.endGroup(); // timeline
settings.setValue("avatar_circles", avatarCircles_);
settings.setValue("decrypt_sidebar", decryptSidebar_);
@@ -467,8 +525,16 @@ UserSettings::save()
settings.setValue("camera_resolution", cameraResolution_);
settings.setValue("camera_frame_rate", cameraFrameRate_);
settings.setValue("use_stun_server", useStunServer_);
+ settings.setValue("currentProfile", profile_);
+
+ QString prefix =
+ (profile_ != "" && profile_ != "default") ? "profile/" + profile_ + "/" : "";
+ settings.setValue(prefix + "auth/access_token", accessToken_);
+ settings.setValue(prefix + "auth/home_server", homeserver_);
+ settings.setValue(prefix + "auth/user_id", userId_);
+ settings.setValue(prefix + "auth/device_id", deviceId_);
- settings.endGroup();
+ settings.endGroup(); // user
settings.sync();
}
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index c699fd59..dd1e26d9 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -84,6 +84,12 @@ class UserSettings : public QObject
bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged)
Q_PROPERTY(bool shareKeysWithTrustedUsers READ shareKeysWithTrustedUsers WRITE
setShareKeysWithTrustedUsers NOTIFY shareKeysWithTrustedUsersChanged)
+ Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged)
+ Q_PROPERTY(QString userId READ userId WRITE setUserId NOTIFY userIdChanged)
+ Q_PROPERTY(
+ QString accessToken READ accessToken WRITE setAccessToken NOTIFY accessTokenChanged)
+ Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged)
+ Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
public:
UserSettings();
@@ -95,7 +101,7 @@ public:
Unavailable,
Offline,
};
- Q_ENUM(Presence);
+ Q_ENUM(Presence)
void save();
void load();
@@ -128,6 +134,11 @@ public:
void setCameraFrameRate(QString frameRate);
void setUseStunServer(bool state);
void setShareKeysWithTrustedUsers(bool state);
+ void setProfile(QString profile);
+ void setUserId(QString userId);
+ void setAccessToken(QString accessToken);
+ void setDeviceId(QString deviceId);
+ void setHomeserver(QString homeserver);
QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
bool messageHoverHighlight() const { return messageHoverHighlight_; }
@@ -161,6 +172,11 @@ public:
QString cameraFrameRate() const { return cameraFrameRate_; }
bool useStunServer() const { return useStunServer_; }
bool shareKeysWithTrustedUsers() const { return shareKeysWithTrustedUsers_; }
+ QString profile() const { return profile_; }
+ QString userId() const { return userId_; }
+ QString accessToken() const { return accessToken_; }
+ QString deviceId() const { return deviceId_; }
+ QString homeserver() const { return homeserver_; }
signals:
void groupViewStateChanged(bool state);
@@ -191,6 +207,11 @@ signals:
void cameraFrameRateChanged(QString frameRate);
void useStunServerChanged(bool state);
void shareKeysWithTrustedUsersChanged(bool state);
+ void profileChanged(QString profile);
+ void userIdChanged(QString userId);
+ void accessTokenChanged(QString accessToken);
+ void deviceIdChanged(QString deviceId);
+ void homeserverChanged(QString homeserver);
private:
// Default to system theme if QT_QPA_PLATFORMTHEME var is set.
@@ -226,6 +247,11 @@ private:
QString cameraResolution_;
QString cameraFrameRate_;
bool useStunServer_;
+ QString profile_;
+ QString userId_;
+ QString accessToken_;
+ QString deviceId_;
+ QString homeserver_;
};
class HorizontalLine : public QFrame
diff --git a/src/main.cpp b/src/main.cpp
index 5eeebb82..58bdda34 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -107,29 +107,7 @@ main(int argc, char *argv[])
// needed for settings so need to register before any settings are read to prevent warnings
qRegisterMetaType<UserSettings::Presence>();
- // This is some hacky programming, but it's necessary (AFAIK?) to get the unique config name
- // parsed before the app name is set.
- QString appName{"nheko"};
- for (int i = 0; i < argc; ++i) {
- if (QString{argv[i]}.startsWith("--profile=")) {
- QString q{argv[i]};
- q.remove("--profile=");
- appName += "-" + q;
- } else if (QString{argv[i]}.startsWith("--p=")) {
- QString q{argv[i]};
- q.remove("-p=");
- appName += "-" + q;
- } else if (QString{argv[i]} == "--profile" || QString{argv[i]} == "-p") {
- if (i < argc - 1) // if i is less than argc - 1, we still have a parameter
- // left to process as the name
- {
- ++i; // the next arg is the name, so increment
- appName += "-" + QString{argv[i]};
- }
- }
- }
-
- QCoreApplication::setApplicationName(appName);
+ QCoreApplication::setApplicationName("nheko");
QCoreApplication::setApplicationVersion(nheko::version);
QCoreApplication::setOrganizationName("nheko");
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
@@ -147,12 +125,36 @@ main(int argc, char *argv[])
}
#endif
+ // This is some hacky programming, but it's necessary (AFAIK?) to get the unique config name
+ // parsed before the SingleApplication userdata is set.
+ QString userdata{""};
+ for (int i = 0; i < argc; ++i) {
+ if (QString{argv[i]}.startsWith("--profile=")) {
+ QString q{argv[i]};
+ q.remove("--profile=");
+ userdata = q;
+ } else if (QString{argv[i]}.startsWith("--p=")) {
+ QString q{argv[i]};
+ q.remove("-p=");
+ userdata = q;
+ } else if (QString{argv[i]} == "--profile" || QString{argv[i]} == "-p") {
+ if (i < argc - 1) // if i is less than argc - 1, we still have a parameter
+ // left to process as the name
+ {
+ ++i; // the next arg is the name, so increment
+ userdata = QString{argv[i]};
+ }
+ }
+ }
+
SingleApplication app(argc,
argv,
false,
SingleApplication::Mode::User |
SingleApplication::Mode::ExcludeAppPath |
- SingleApplication::Mode::ExcludeAppVersion);
+ SingleApplication::Mode::ExcludeAppVersion,
+ 100,
+ userdata);
QCommandLineParser parser;
parser.addHelpOption();
@@ -194,14 +196,17 @@ main(int argc, char *argv[])
std::exit(1);
}
- QSettings settings;
+ UserSettings settings;
+
+ if (parser.isSet(configName))
+ settings.setProfile(parser.value(configName));
QFont font;
- QString userFontFamily = settings.value("user/font_family", "").toString();
+ QString userFontFamily = settings.font();
if (!userFontFamily.isEmpty()) {
font.setFamily(userFontFamily);
}
- font.setPointSizeF(settings.value("user/font_size", font.pointSizeF()).toDouble());
+ font.setPointSizeF(settings.fontSize());
app.setFont(font);
@@ -216,13 +221,12 @@ main(int argc, char *argv[])
appTranslator.load(QLocale(), "nheko", "_", ":/translations");
app.installTranslator(&appTranslator);
- MainWindow w{(appName == "nheko" ? "" : appName.remove("nheko-"))};
+ MainWindow w;
// Move the MainWindow to the center
w.move(screenCenter(w.width(), w.height()));
- if (!settings.value("user/window/start_in_tray", false).toBool() ||
- !settings.value("user/window/tray", true).toBool())
+ if (!settings.startInTray() && !settings.tray())
w.show();
QObject::connect(&app, &QApplication::aboutToQuit, &w, [&w]() {
diff --git a/third_party/SingleApplication-3.1.3.1/singleapplication.cpp b/third_party/SingleApplication-3.1.3.1/singleapplication.cpp
deleted file mode 100644
index 9af38804..00000000
--- a/third_party/SingleApplication-3.1.3.1/singleapplication.cpp
+++ /dev/null
@@ -1,201 +0,0 @@
-// The MIT License (MIT)
-//
-// Copyright (c) Itay Grudev 2015 - 2020
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-#include <QtCore/QElapsedTimer>
-#include <QtCore/QThread>
-#include <QtCore/QByteArray>
-#include <QtCore/QSharedMemory>
-#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
-#include <QtCore/QRandomGenerator>
-#else
-#include <QtCore/QDateTime>
-#endif
-
-#include "singleapplication.h"
-#include "singleapplication_p.h"
-
-/**
- * @brief Constructor. Checks and fires up LocalServer or closes the program
- * if another instance already exists
- * @param argc
- * @param argv
- * @param {bool} allowSecondaryInstances
- */
-SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout )
- : app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
-{
- Q_D(SingleApplication);
-
-#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
- // On Android and iOS since the library is not supported fallback to
- // standard QApplication behaviour by simply returning at this point.
- qWarning() << "SingleApplication is not supported on Android and iOS systems.";
- return;
-#endif
-
- // Store the current mode of the program
- d->options = options;
-
- // Generating an application ID used for identifying the shared memory
- // block and QLocalServer
- d->genBlockServerName();
-
-#ifdef Q_OS_UNIX
- // By explicitly attaching it and then deleting it we make sure that the
- // memory is deleted even after the process has crashed on Unix.
- d->memory = new QSharedMemory( d->blockServerName );
- d->memory->attach();
- delete d->memory;
-#endif
- // Guarantee thread safe behaviour with a shared memory block.
- d->memory = new QSharedMemory( d->blockServerName );
-
- // Create a shared memory block
- if( d->memory->create( sizeof( InstancesInfo ) ) ) {
- // Initialize the shared memory block
- d->memory->lock();
- d->initializeMemoryBlock();
- d->memory->unlock();
- } else {
- // Attempt to attach to the memory segment
- if( ! d->memory->attach() ) {
- qCritical() << "SingleApplication: Unable to attach to shared memory block.";
- qCritical() << d->memory->errorString();
- delete d;
- ::exit( EXIT_FAILURE );
- }
- }
-
- auto *inst = static_cast<InstancesInfo*>( d->memory->data() );
- QElapsedTimer time;
- time.start();
-
- // Make sure the shared memory block is initialised and in consistent state
- while( true ) {
- d->memory->lock();
-
- if( d->blockChecksum() == inst->checksum ) break;
-
- if( time.elapsed() > 5000 ) {
- qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
- d->initializeMemoryBlock();
- }
-
- d->memory->unlock();
-
- // Random sleep here limits the probability of a collision between two racing apps
-#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
- QThread::sleep( QRandomGenerator::global()->bounded( 8u, 18u ) );
-#else
- qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
- QThread::sleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ) );
-#endif
- }
-
- if( inst->primary == false) {
- d->startPrimary();
- d->memory->unlock();
- return;
- }
-
- // Check if another instance can be started
- if( allowSecondary ) {
- inst->secondary += 1;
- inst->checksum = d->blockChecksum();
- d->instanceNumber = inst->secondary;
- d->startSecondary();
- if( d->options & Mode::SecondaryNotification ) {
- d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance );
- }
- d->memory->unlock();
- return;
- }
-
- d->memory->unlock();
-
- d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance );
-
- delete d;
-
- ::exit( EXIT_SUCCESS );
-}
-
-/**
- * @brief Destructor
- */
-SingleApplication::~SingleApplication()
-{
- Q_D(SingleApplication);
- delete d;
-}
-
-bool SingleApplication::isPrimary()
-{
- Q_D(SingleApplication);
- return d->server != nullptr;
-}
-
-bool SingleApplication::isSecondary()
-{
- Q_D(SingleApplication);
- return d->server == nullptr;
-}
-
-quint32 SingleApplication::instanceId()
-{
- Q_D(SingleApplication);
- return d->instanceNumber;
-}
-
-qint64 SingleApplication::primaryPid()
-{
- Q_D(SingleApplication);
- return d->primaryPid();
-}
-
-QString SingleApplication::primaryUser()
-{
- Q_D(SingleApplication);
- return d->primaryUser();
-}
-
-QString SingleApplication::currentUser()
-{
- Q_D(SingleApplication);
- return d->getUsername();
-}
-
-bool SingleApplication::sendMessage( const QByteArray &message, int timeout )
-{
- Q_D(SingleApplication);
-
- // Nobody to connect to
- if( isPrimary() ) return false;
-
- // Make sure the socket is connected
- d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect );
-
- d->socket->write( message );
- bool dataWritten = d->socket->waitForBytesWritten( timeout );
- d->socket->flush();
- return dataWritten;
-}
diff --git a/third_party/SingleApplication-3.1.3.1/.gitignore b/third_party/SingleApplication-3.2.0-dc8042b/.gitignore
index 35533fe8..35533fe8 100644
--- a/third_party/SingleApplication-3.1.3.1/.gitignore
+++ b/third_party/SingleApplication-3.2.0-dc8042b/.gitignore
diff --git a/third_party/SingleApplication-3.1.3.1/CHANGELOG.md b/third_party/SingleApplication-3.2.0-dc8042b/CHANGELOG.md
index 3662b0b2..e2ba290e 100644
--- a/third_party/SingleApplication-3.1.3.1/CHANGELOG.md
+++ b/third_party/SingleApplication-3.2.0-dc8042b/CHANGELOG.md
@@ -3,6 +3,30 @@ Changelog
If by accident I have forgotten to credit someone in the CHANGELOG, email me and I will fix it.
+__3.2.0__
+---------
+
+* Added support for Qt 6 - _Jonas Kvinge_
+* Fixed warning in `Qt 5.9` with `min`/`max` functions on Windows - _Nick Korotysh_
+* Fix return value of connectToPrimary() when connect is successful - _Jonas Kvinge_
+* Fix build issue with MinGW GCC pedantic mode - _Iakov Kirilenko_
+* Fixed conversion from `int` to `quint32` and Clang Tidy warnings - _Hennadii Chernyshchyk_
+
+__3.1.5__
+---------
+
+* Improved library stability in edge cases and very rapid process initialisation
+* Fixed Bug where the shared memory block may have been modified without a lock
+* Fixed Bug causing `instanceStarted()` to not get emitted when a second instance
+ has been started before the primary has initiated it's `QLocalServer`.
+
+__3.1.4__
+---------
+* Officially supporting and build-testing against Qt 5.15
+* Fixed an MSVC C4996 warning that suggests using `strncpy_s`.
+
+ _Hennadii Chernyshchyk_
+
__3.1.3.1__
---------
* CMake build system improvements
@@ -38,18 +62,18 @@ __3.1.0a__
__3.0.19__
----------
-* Fixed code warning for depricated functions in Qt 5.10 related to `QTime` and `qrand()`.
+* Fixed code warning for depricated functions in Qt 5.10 related to `QTime` and `qrand()`.
_Hennadii Chernyshchyk_
_Anton Filimonov_
_Jonas Kvinge_
-
+
__3.0.18__
----------
* Fallback to standard QApplication class on iOS and Android systems where
the library is not supported.
-
+
* Added Build CI tests to verify the library builds successfully on Linux, Windows and MacOS across multiple Qt versions.
_Anton Filimonov_
diff --git a/third_party/SingleApplication-3.1.3.1/CMakeLists.txt b/third_party/SingleApplication-3.2.0-dc8042b/CMakeLists.txt
index 85dba84c..ae1b1439 100644
--- a/third_party/SingleApplication-3.1.3.1/CMakeLists.txt
+++ b/third_party/SingleApplication-3.2.0-dc8042b/CMakeLists.txt
@@ -10,22 +10,28 @@ add_library(${PROJECT_NAME} STATIC
)
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
+if(NOT QT_DEFAULT_MAJOR_VERSION)
+ set(QT_DEFAULT_MAJOR_VERSION 5 CACHE STRING "Qt version to use (5 or 6), defaults to 5")
+endif()
+
# Find dependencies
-find_package(Qt5 COMPONENTS Network REQUIRED)
-target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Network)
+set(QT_COMPONENTS Core Network)
+set(QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Core Qt${QT_DEFAULT_MAJOR_VERSION}::Network)
if(QAPPLICATION_CLASS STREQUAL QApplication)
- find_package(Qt5 COMPONENTS Widgets REQUIRED)
- target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Widgets)
+ list(APPEND QT_COMPONENTS Widgets)
+ list(APPEND QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Widgets)
elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication)
- find_package(Qt5 COMPONENTS Gui REQUIRED)
- target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Gui)
+ list(APPEND QT_COMPONENTS Gui)
+ list(APPEND QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Gui)
else()
set(QAPPLICATION_CLASS QCoreApplication)
- find_package(Qt5 COMPONENTS Core REQUIRED)
- target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Core)
endif()
+find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS ${QT_COMPONENTS} REQUIRED)
+
+target_link_libraries(${PROJECT_NAME} PUBLIC ${QT_LIBRARIES})
+
if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE advapi32)
endif()
diff --git a/third_party/SingleApplication-3.1.3.1/LICENSE b/third_party/SingleApplication-3.2.0-dc8042b/LICENSE
index a82e5a68..a82e5a68 100644
--- a/third_party/SingleApplication-3.1.3.1/LICENSE
+++ b/third_party/SingleApplication-3.2.0-dc8042b/LICENSE
diff --git a/third_party/SingleApplication-3.1.3.1/README.md b/third_party/SingleApplication-3.2.0-dc8042b/README.md
index 3c36b557..457ab339 100644
--- a/third_party/SingleApplication-3.1.3.1/README.md
+++ b/third_party/SingleApplication-3.2.0-dc8042b/README.md
@@ -2,7 +2,7 @@ SingleApplication
=================
[](https://github.com/itay-grudev/SingleApplication/actions)
-This is a replacement of the QtSingleApplication for `Qt5`.
+This is a replacement of the QtSingleApplication for `Qt5` and `Qt6`.
Keeps the Primary Instance of your Application and kills each subsequent
instances. It can (if enabled) spawn secondary (non-related to the primary)
@@ -139,13 +139,22 @@ app.isSecondary();
*__Note:__ If your Primary Instance is terminated a newly launched instance
will replace the Primary one even if the Secondary flag has been set.*
+Examples
+--------
+
+There are three examples provided in this repository:
+
+* Basic example that prevents a secondary instance from starting [`examples/basic`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/basic)
+* An example of a graphical application raising it's parent window [`examples/calculator`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/calculator)
+* A console application sending the primary instance it's command line parameters [`examples/sending_arguments`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/sending_arguments)
+
API
---
### Members
```cpp
-SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100 )
+SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100, QString userData = QString() )
```
Depending on whether `allowSecondary` is set, this constructor may terminate
@@ -154,7 +163,7 @@ can be specified to set whether the SingleApplication block should work
user-wide or system-wide. Additionally the `Mode::SecondaryNotification` may be
used to notify the primary instance whenever a secondary instance had been
started (disabled by default). `timeout` specifies the maximum time in
-milliseconds to wait for blocking operations.
+milliseconds to wait for blocking operations. Setting `userData` provides additional data that will isolate this instance from other instances that do not have the same (or any) user data set.
*__Note:__ `argc` and `argv` may be changed as Qt removes arguments that it
recognizes.*
diff --git a/third_party/SingleApplication-3.1.3.1/SingleApplication b/third_party/SingleApplication-3.2.0-dc8042b/SingleApplication
index 8ead1a42..8ead1a42 100644
--- a/third_party/SingleApplication-3.1.3.1/SingleApplication
+++ b/third_party/SingleApplication-3.2.0-dc8042b/SingleApplication
diff --git a/third_party/SingleApplication-3.1.3.1/Windows.md b/third_party/SingleApplication-3.2.0-dc8042b/Windows.md
index 13c52da0..13c52da0 100644
--- a/third_party/SingleApplication-3.1.3.1/Windows.md
+++ b/third_party/SingleApplication-3.2.0-dc8042b/Windows.md
diff --git a/third_party/SingleApplication-3.2.0-dc8042b/singleapplication.cpp b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication.cpp
new file mode 100644
index 00000000..276ceee9
--- /dev/null
+++ b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication.cpp
@@ -0,0 +1,274 @@
+// The MIT License (MIT)
+//
+// Copyright (c) Itay Grudev 2015 - 2020
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <QtCore/QElapsedTimer>
+#include <QtCore/QByteArray>
+#include <QtCore/QSharedMemory>
+
+#include "singleapplication.h"
+#include "singleapplication_p.h"
+
+/**
+ * @brief Constructor. Checks and fires up LocalServer or closes the program
+ * if another instance already exists
+ * @param argc
+ * @param argv
+ * @param allowSecondary Whether to enable secondary instance support
+ * @param options Optional flags to toggle specific behaviour
+ * @param timeout Maximum time blocking functions are allowed during app load
+ */
+SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, QString userData )
+ : app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
+{
+ Q_D( SingleApplication );
+
+#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
+ // On Android and iOS since the library is not supported fallback to
+ // standard QApplication behaviour by simply returning at this point.
+ qWarning() << "SingleApplication is not supported on Android and iOS systems.";
+ return;
+#endif
+
+ // Store the current mode of the program
+ d->options = options;
+
+ // Add any unique user data
+ if ( ! userData.isEmpty() )
+ d->addAppData( userData );
+
+ // Generating an application ID used for identifying the shared memory
+ // block and QLocalServer
+ d->genBlockServerName();
+
+ // To mitigate QSharedMemory issues with large amount of processes
+ // attempting to attach at the same time
+ SingleApplicationPrivate::randomSleep();
+
+#ifdef Q_OS_UNIX
+ // By explicitly attaching it and then deleting it we make sure that the
+ // memory is deleted even after the process has crashed on Unix.
+ d->memory = new QSharedMemory( d->blockServerName );
+ d->memory->attach();
+ delete d->memory;
+#endif
+ // Guarantee thread safe behaviour with a shared memory block.
+ d->memory = new QSharedMemory( d->blockServerName );
+
+ // Create a shared memory block
+ if( d->memory->create( sizeof( InstancesInfo ) )){
+ // Initialize the shared memory block
+ if( ! d->memory->lock() ){
+ qCritical() << "SingleApplication: Unable to lock memory block after create.";
+ abortSafely();
+ }
+ d->initializeMemoryBlock();
+ } else {
+ if( d->memory->error() == QSharedMemory::AlreadyExists ){
+ // Attempt to attach to the memory segment
+ if( ! d->memory->attach() ){
+ qCritical() << "SingleApplication: Unable to attach to shared memory block.";
+ abortSafely();
+ }
+ if( ! d->memory->lock() ){
+ qCritical() << "SingleApplication: Unable to lock memory block after attach.";
+ abortSafely();
+ }
+ } else {
+ qCritical() << "SingleApplication: Unable to create block.";
+ abortSafely();
+ }
+ }
+
+ auto *inst = static_cast<InstancesInfo*>( d->memory->data() );
+ QElapsedTimer time;
+ time.start();
+
+ // Make sure the shared memory block is initialised and in consistent state
+ while( true ){
+ // If the shared memory block's checksum is valid continue
+ if( d->blockChecksum() == inst->checksum ) break;
+
+ // If more than 5s have elapsed, assume the primary instance crashed and
+ // assume it's position
+ if( time.elapsed() > 5000 ){
+ qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
+ d->initializeMemoryBlock();
+ }
+
+ // Otherwise wait for a random period and try again. The random sleep here
+ // limits the probability of a collision between two racing apps and
+ // allows the app to initialise faster
+ if( ! d->memory->unlock() ){
+ qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
+ qDebug() << d->memory->errorString();
+ }
+ SingleApplicationPrivate::randomSleep();
+ if( ! d->memory->lock() ){
+ qCritical() << "SingleApplication: Unable to lock memory after random wait.";
+ abortSafely();
+ }
+ }
+
+ if( inst->primary == false ){
+ d->startPrimary();
+ if( ! d->memory->unlock() ){
+ qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
+ qDebug() << d->memory->errorString();
+ }
+ return;
+ }
+
+ // Check if another instance can be started
+ if( allowSecondary ){
+ d->startSecondary();
+ if( d->options & Mode::SecondaryNotification ){
+ d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance );
+ }
+ if( ! d->memory->unlock() ){
+ qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
+ qDebug() << d->memory->errorString();
+ }
+ return;
+ }
+
+ if( ! d->memory->unlock() ){
+ qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
+ qDebug() << d->memory->errorString();
+ }
+
+ d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance );
+
+ delete d;
+
+ ::exit( EXIT_SUCCESS );
+}
+
+SingleApplication::~SingleApplication()
+{
+ Q_D( SingleApplication );
+ delete d;
+}
+
+/**
+ * Checks if the current application instance is primary.
+ * @return Returns true if the instance is primary, false otherwise.
+ */
+bool SingleApplication::isPrimary()
+{
+ Q_D( SingleApplication );
+ return d->server != nullptr;
+}
+
+/**
+ * Checks if the current application instance is secondary.
+ * @return Returns true if the instance is secondary, false otherwise.
+ */
+bool SingleApplication::isSecondary()
+{
+ Q_D( SingleApplication );
+ return d->server == nullptr;
+}
+
+/**
+ * Allows you to identify an instance by returning unique consecutive instance
+ * ids. It is reset when the first (primary) instance of your app starts and
+ * only incremented afterwards.
+ * @return Returns a unique instance id.
+ */
+quint32 SingleApplication::instanceId()
+{
+ Q_D( SingleApplication );
+ return d->instanceNumber;
+}
+
+/**
+ * Returns the OS PID (Process Identifier) of the process running the primary
+ * instance. Especially useful when SingleApplication is coupled with OS.
+ * specific APIs.
+ * @return Returns the primary instance PID.
+ */
+qint64 SingleApplication::primaryPid()
+{
+ Q_D( SingleApplication );
+ return d->primaryPid();
+}
+
+/**
+ * Returns the username the primary instance is running as.
+ * @return Returns the username the primary instance is running as.
+ */
+QString SingleApplication::primaryUser()
+{
+ Q_D( SingleApplication );
+ return d->primaryUser();
+}
+
+/**
+ * Returns the username the current instance is running as.
+ * @return Returns the username the current instance is running as.
+ */
+QString SingleApplication::currentUser()
+{
+ return SingleApplicationPrivate::getUsername();
+}
+
+/**
+ * Sends message to the Primary Instance.
+ * @param message The message to send.
+ * @param timeout the maximum timeout in milliseconds for blocking functions.
+ * @return true if the message was sent successfuly, false otherwise.
+ */
+bool SingleApplication::sendMessage( const QByteArray &message, int timeout )
+{
+ Q_D( SingleApplication );
+
+ // Nobody to connect to
+ if( isPrimary() ) return false;
+
+ // Make sure the socket is connected
+ if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) )
+ return false;
+
+ d->socket->write( message );
+ bool dataWritten = d->socket->waitForBytesWritten( timeout );
+ d->socket->flush();
+ return dataWritten;
+}
+
+/**
+ * Cleans up the shared memory block and exits with a failure.
+ * This function halts program execution.
+ */
+void SingleApplication::abortSafely()
+{
+ Q_D( SingleApplication );
+
+ qCritical() << "SingleApplication: " << d->memory->error() << d->memory->errorString();
+ delete d;
+ ::exit( EXIT_FAILURE );
+}
+
+QStringList SingleApplication::userData()
+{
+ Q_D( SingleApplication );
+ return d->appData();
+}
diff --git a/third_party/SingleApplication-3.1.3.1/singleapplication.h b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication.h
index fd806a3d..d39a6614 100644
--- a/third_party/SingleApplication-3.1.3.1/singleapplication.h
+++ b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication.h
@@ -85,7 +85,7 @@ public:
* Usually 4*timeout would be the worst case (fail) scenario.
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
*/
- explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000 );
+ explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000, QString userData = QString() );
~SingleApplication() override;
/**
@@ -133,6 +133,12 @@ public:
*/
bool sendMessage( const QByteArray &message, int timeout = 100 );
+ /**
+ * @brief Get the set user data.
+ * @returns {QStringList}
+ */
+ QStringList userData();
+
Q_SIGNALS:
void instanceStarted();
void receivedMessage( quint32 instanceId, QByteArray message );
@@ -140,6 +146,7 @@ Q_SIGNALS:
private:
SingleApplicationPrivate *d_ptr;
Q_DECLARE_PRIVATE(SingleApplication)
+ void abortSafely();
};
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
diff --git a/third_party/SingleApplication-3.1.3.1/singleapplication.pri b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication.pri
index ae81f599..ae81f599 100644
--- a/third_party/SingleApplication-3.1.3.1/singleapplication.pri
+++ b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication.pri
diff --git a/third_party/SingleApplication-3.1.3.1/singleapplication_p.cpp b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication_p.cpp
index 705609f2..1ab58c23 100644
--- a/third_party/SingleApplication-3.1.3.1/singleapplication_p.cpp
+++ b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication_p.cpp
@@ -33,12 +33,20 @@
#include <cstddef>
#include <QtCore/QDir>
+#include <QtCore/QThread>
#include <QtCore/QByteArray>
#include <QtCore/QDataStream>
+#include <QtCore/QElapsedTimer>
#include <QtCore/QCryptographicHash>
#include <QtNetwork/QLocalServer>
#include <QtNetwork/QLocalSocket>
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
+#include <QtCore/QRandomGenerator>
+#else
+#include <QtCore/QDateTime>
+#endif
+
#include "singleapplication.h"
#include "singleapplication_p.h"
@@ -49,6 +57,9 @@
#endif
#ifdef Q_OS_WIN
+ #ifndef NOMINMAX
+ #define NOMINMAX 1
+ #endif
#include <windows.h>
#include <lmcons.h>
#endif
@@ -59,20 +70,20 @@ SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr )
server = nullptr;
socket = nullptr;
memory = nullptr;
- instanceNumber = -1;
+ instanceNumber = 0;
}
SingleApplicationPrivate::~SingleApplicationPrivate()
{
- if( socket != nullptr ) {
+ if( socket != nullptr ){
socket->close();
delete socket;
}
- if( memory != nullptr ) {
+ if( memory != nullptr ){
memory->lock();
auto *inst = static_cast<InstancesInfo*>(memory->data());
- if( server != nullptr ) {
+ if( server != nullptr ){
server->close();
delete server;
inst->primary = false;
@@ -106,7 +117,7 @@ QString SingleApplicationPrivate::getUsername()
struct passwd *pw = getpwuid( uid );
if( pw )
username = QString::fromLocal8Bit( pw->pw_name );
- if ( username.isEmpty() ) {
+ if ( username.isEmpty() ){
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
username = QString::fromLocal8Bit( qgetenv( "USER" ) );
#else
@@ -125,11 +136,14 @@ void SingleApplicationPrivate::genBlockServerName()
appData.addData( SingleApplication::app_t::organizationName().toUtf8() );
appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() );
- if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ) {
+ if ( ! appDataList.isEmpty() )
+ appData.addData( appDataList.join( "" ).toUtf8() );
+
+ if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){
appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() );
}
- if( ! (options & SingleApplication::Mode::ExcludeAppPath) ) {
+ if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){
#ifdef Q_OS_WIN
appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() );
#else
@@ -138,7 +152,7 @@ void SingleApplicationPrivate::genBlockServerName()
}
// User level block requires a user specific data in the hash
- if( options & SingleApplication::Mode::User ) {
+ if( options & SingleApplication::Mode::User ){
appData.addData( getUsername().toUtf8() );
}
@@ -147,7 +161,7 @@ void SingleApplicationPrivate::genBlockServerName()
blockServerName = appData.result().toBase64().replace("/", "_");
}
-void SingleApplicationPrivate::initializeMemoryBlock()
+void SingleApplicationPrivate::initializeMemoryBlock() const
{
auto *inst = static_cast<InstancesInfo*>( memory->data() );
inst->primary = false;
@@ -159,8 +173,14 @@ void SingleApplicationPrivate::initializeMemoryBlock()
void SingleApplicationPrivate::startPrimary()
{
- Q_Q(SingleApplication);
+ // Reset the number of connections
+ auto *inst = static_cast <InstancesInfo*>( memory->data() );
+ inst->primary = true;
+ inst->primaryPid = QCoreApplication::applicationPid();
+ qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) );
+ inst->checksum = blockChecksum();
+ instanceNumber = 0;
// Successful creation means that no main process exists
// So we start a QLocalServer to listen for connections
QLocalServer::removeServer( blockServerName );
@@ -168,10 +188,10 @@ void SingleApplicationPrivate::startPrimary()
// Restrict access to the socket according to the
// SingleApplication::Mode::User flag on User level or no restrictions
- if( options & SingleApplication::Mode::User ) {
- server->setSocketOptions( QLocalServer::UserAccessOption );
+ if( options & SingleApplication::Mode::User ){
+ server->setSocketOptions( QLocalServer::UserAccessOption );
} else {
- server->setSocketOptions( QLocalServer::WorldAccessOption );
+ server->setSocketOptions( QLocalServer::WorldAccessOption );
}
server->listen( blockServerName );
@@ -181,87 +201,95 @@ void SingleApplicationPrivate::startPrimary()
this,
&SingleApplicationPrivate::slotConnectionEstablished
);
-
- // Reset the number of connections
- auto *inst = static_cast <InstancesInfo*>( memory->data() );
-
- inst->primary = true;
- inst->primaryPid = q->applicationPid();
- strncpy( inst->primaryUser, getUsername().toUtf8().data(), 127 );
- inst->primaryUser[127] = '\0';
- inst->checksum = blockChecksum();
-
- instanceNumber = 0;
}
void SingleApplicationPrivate::startSecondary()
{
+ auto *inst = static_cast <InstancesInfo*>( memory->data() );
+
+ inst->secondary += 1;
+ inst->checksum = blockChecksum();
+ instanceNumber = inst->secondary;
}
-void SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
+bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
{
+ QElapsedTimer time;
+ time.start();
+
// Connect to the Local Server of the Primary Instance if not already
// connected.
- if( socket == nullptr ) {
+ if( socket == nullptr ){
socket = new QLocalSocket();
}
- // If already connected - we are done;
- if( socket->state() == QLocalSocket::ConnectedState )
- return;
+ if( socket->state() == QLocalSocket::ConnectedState ) return true;
- // If not connect
- if( socket->state() == QLocalSocket::UnconnectedState ||
- socket->state() == QLocalSocket::ClosingState ) {
- socket->connectToServer( blockServerName );
- }
+ if( socket->state() != QLocalSocket::ConnectedState ){
+
+ while( true ){
+ randomSleep();
- // Wait for being connected
- if( socket->state() == QLocalSocket::ConnectingState ) {
- socket->waitForConnected( msecs );
+ if( socket->state() != QLocalSocket::ConnectingState )
+ socket->connectToServer( blockServerName );
+
+ if( socket->state() == QLocalSocket::ConnectingState ){
+ socket->waitForConnected( static_cast<int>(msecs - time.elapsed()) );
+ }
+
+ // If connected break out of the loop
+ if( socket->state() == QLocalSocket::ConnectedState ) break;
+
+ // If elapsed time since start is longer than the method timeout return
+ if( time.elapsed() >= msecs ) return false;
+ }
}
// Initialisation message according to the SingleApplication protocol
- if( socket->state() == QLocalSocket::ConnectedState ) {
- // Notify the parent that a new instance had been started;
- QByteArray initMsg;
- QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
+ QByteArray initMsg;
+ QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
- writeStream.setVersion(QDataStream::Qt_5_6);
+ writeStream.setVersion(QDataStream::Qt_5_6);
#endif
- writeStream << blockServerName.toLatin1();
- writeStream << static_cast<quint8>(connectionType);
- writeStream << instanceNumber;
- quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
- writeStream << checksum;
+ writeStream << blockServerName.toLatin1();
+ writeStream << static_cast<quint8>(connectionType);
+ writeStream << instanceNumber;
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
+#else
+ quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
+#endif
+ writeStream << checksum;
- // The header indicates the message length that follows
- QByteArray header;
- QDataStream headerStream(&header, QIODevice::WriteOnly);
+ // The header indicates the message length that follows
+ QByteArray header;
+ QDataStream headerStream(&header, QIODevice::WriteOnly);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
- headerStream.setVersion(QDataStream::Qt_5_6);
+ headerStream.setVersion(QDataStream::Qt_5_6);
#endif
- headerStream << static_cast <quint64>( initMsg.length() );
+ headerStream << static_cast <quint64>( initMsg.length() );
- socket->write( header );
- socket->write( initMsg );
- socket->flush();
- socket->waitForBytesWritten( msecs );
- }
+ socket->write( header );
+ socket->write( initMsg );
+ bool result = socket->waitForBytesWritten( static_cast<int>(msecs - time.elapsed()) );
+ socket->flush();
+ return result;
}
-quint16 SingleApplicationPrivate::blockChecksum()
+quint16 SingleApplicationPrivate::blockChecksum() const
{
- return qChecksum(
- static_cast <const char *>( memory->data() ),
- offsetof( InstancesInfo, checksum )
- );
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum)));
+#else
+ quint16 checksum = qChecksum(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum));
+#endif
+ return checksum;
}
-qint64 SingleApplicationPrivate::primaryPid()
+qint64 SingleApplicationPrivate::primaryPid() const
{
qint64 pid;
@@ -273,7 +301,7 @@ qint64 SingleApplicationPrivate::primaryPid()
return pid;
}
-QString SingleApplicationPrivate::primaryUser()
+QString SingleApplicationPrivate::primaryUser() const
{
QByteArray username;
@@ -294,7 +322,7 @@ void SingleApplicationPrivate::slotConnectionEstablished()
connectionMap.insert(nextConnSocket, ConnectionInfo());
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
- [nextConnSocket, this]() {
+ [nextConnSocket, this](){
auto &info = connectionMap[nextConnSocket];
Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
}
@@ -308,9 +336,9 @@ void SingleApplicationPrivate::slotConnectionEstablished()
);
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
- [nextConnSocket, this]() {
+ [nextConnSocket, this](){
auto &info = connectionMap[nextConnSocket];
- switch(info.stage) {
+ switch(info.stage){
case StageHeader:
readInitMessageHeader(nextConnSocket);
break;
@@ -329,11 +357,11 @@ void SingleApplicationPrivate::slotConnectionEstablished()
void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
{
- if (!connectionMap.contains( sock )) {
+ if (!connectionMap.contains( sock )){
return;
}
- if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ) {
+ if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){
return;
}
@@ -350,7 +378,7 @@ void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
info.stage = StageBody;
info.msgLen = msgLen;
- if ( sock->bytesAvailable() >= (qint64) msgLen ) {
+ if ( sock->bytesAvailable() >= (qint64) msgLen ){
readInitMessageBody( sock );
}
}
@@ -359,12 +387,12 @@ void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
{
Q_Q(SingleApplication);
- if (!connectionMap.contains( sock )) {
+ if (!connectionMap.contains( sock )){
return;
}
ConnectionInfo &info = connectionMap[sock];
- if( sock->bytesAvailable() < ( qint64 )info.msgLen ) {
+ if( sock->bytesAvailable() < ( qint64 )info.msgLen ){
return;
}
@@ -394,13 +422,17 @@ void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
quint16 msgChecksum = 0;
readStream >> msgChecksum;
- const quint16 actualChecksum = qChecksum( msgBytes.constData(), static_cast<quint32>( msgBytes.length() - sizeof( quint16 ) ) );
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
+#else
+ const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
+#endif
bool isValid = readStream.status() == QDataStream::Ok &&
QLatin1String(latin1Name) == blockServerName &&
msgChecksum == actualChecksum;
- if( !isValid ) {
+ if( !isValid ){
sock->close();
return;
}
@@ -415,7 +447,7 @@ void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
Q_EMIT q->instanceStarted();
}
- if (sock->bytesAvailable() > 0) {
+ if (sock->bytesAvailable() > 0){
Q_EMIT this->slotDataAvailable( sock, instanceId );
}
}
@@ -431,3 +463,23 @@ void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedS
if( closedSocket->bytesAvailable() > 0 )
Q_EMIT slotDataAvailable( closedSocket, instanceId );
}
+
+void SingleApplicationPrivate::randomSleep()
+{
+#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
+ QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u ));
+#else
+ qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
+ QThread::msleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ));
+#endif
+}
+
+void SingleApplicationPrivate::addAppData(const QString &data)
+{
+ appDataList.push_back(data);
+}
+
+QStringList SingleApplicationPrivate::appData() const
+{
+ return appDataList;
+}
diff --git a/third_party/SingleApplication-3.1.3.1/singleapplication_p.h b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication_p.h
index 29ba346b..c49a46dd 100644
--- a/third_party/SingleApplication-3.1.3.1/singleapplication_p.h
+++ b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication_p.h
@@ -41,8 +41,8 @@ struct InstancesInfo {
bool primary;
quint32 secondary;
qint64 primaryPid;
- quint16 checksum;
char primaryUser[128];
+ quint16 checksum; // Must be the last field
};
struct ConnectionInfo {
@@ -70,17 +70,20 @@ public:
SingleApplicationPrivate( SingleApplication *q_ptr );
~SingleApplicationPrivate() override;
- QString getUsername();
+ static QString getUsername();
void genBlockServerName();
- void initializeMemoryBlock();
+ void initializeMemoryBlock() const;
void startPrimary();
void startSecondary();
- void connectToPrimary(int msecs, ConnectionType connectionType );
- quint16 blockChecksum();
- qint64 primaryPid();
- QString primaryUser();
+ bool connectToPrimary( int msecs, ConnectionType connectionType );
+ quint16 blockChecksum() const;
+ qint64 primaryPid() const;
+ QString primaryUser() const;
void readInitMessageHeader(QLocalSocket *socket);
void readInitMessageBody(QLocalSocket *socket);
+ static void randomSleep();
+ void addAppData(const QString &data);
+ QStringList appData() const;
SingleApplication *q_ptr;
QSharedMemory *memory;
@@ -90,6 +93,7 @@ public:
QString blockServerName;
SingleApplication::Options options;
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
+ QStringList appDataList;
public Q_SLOTS:
void slotConnectionEstablished();
|