From 53f45bdb1c9ee2b42b88ca4587775756f8daa124 Mon Sep 17 00:00:00 2001
From: LorenDB <computersemiexpert@outlook.com>
Date: Mon, 9 Nov 2020 21:28:41 -0500
Subject: [PATCH] Switch profile code to a more flexible method

This introduces a new version of SingleApplication as well.
---
 CMakeLists.txt                                |   2 +-
 README.md                                     |   2 +-
 src/Cache.cpp                                 |  43 ++-
 src/MainWindow.cpp                            |  41 +--
 src/MainWindow.h                              |   4 +-
 src/UserSettingsPage.cpp                      |  72 ++++-
 src/UserSettingsPage.h                        |  28 +-
 src/main.cpp                                  |  64 ++--
 .../singleapplication.cpp                     | 201 -------------
 .../.gitignore                                |   0
 .../CHANGELOG.md                              |  30 +-
 .../CMakeLists.txt                            |  22 +-
 .../LICENSE                                   |   0
 .../README.md                                 |  15 +-
 .../SingleApplication                         |   0
 .../Windows.md                                |   0
 .../singleapplication.cpp                     | 274 ++++++++++++++++++
 .../singleapplication.h                       |   9 +-
 .../singleapplication.pri                     |   0
 .../singleapplication_p.cpp                   | 202 ++++++++-----
 .../singleapplication_p.h                     |  18 +-
 21 files changed, 654 insertions(+), 373 deletions(-)
 delete mode 100644 third_party/SingleApplication-3.1.3.1/singleapplication.cpp
 rename third_party/{SingleApplication-3.1.3.1 => SingleApplication-3.2.0-dc8042b}/.gitignore (100%)
 rename third_party/{SingleApplication-3.1.3.1 => SingleApplication-3.2.0-dc8042b}/CHANGELOG.md (88%)
 rename third_party/{SingleApplication-3.1.3.1 => SingleApplication-3.2.0-dc8042b}/CMakeLists.txt (53%)
 rename third_party/{SingleApplication-3.1.3.1 => SingleApplication-3.2.0-dc8042b}/LICENSE (100%)
 rename third_party/{SingleApplication-3.1.3.1 => SingleApplication-3.2.0-dc8042b}/README.md (90%)
 rename third_party/{SingleApplication-3.1.3.1 => SingleApplication-3.2.0-dc8042b}/SingleApplication (100%)
 rename third_party/{SingleApplication-3.1.3.1 => SingleApplication-3.2.0-dc8042b}/Windows.md (100%)
 create mode 100644 third_party/SingleApplication-3.2.0-dc8042b/singleapplication.cpp
 rename third_party/{SingleApplication-3.1.3.1 => SingleApplication-3.2.0-dc8042b}/singleapplication.h (96%)
 rename third_party/{SingleApplication-3.1.3.1 => SingleApplication-3.2.0-dc8042b}/singleapplication.pri (100%)
 rename third_party/{SingleApplication-3.1.3.1 => SingleApplication-3.2.0-dc8042b}/singleapplication_p.cpp (67%)
 rename third_party/{SingleApplication-3.1.3.1 => SingleApplication-3.2.0-dc8042b}/singleapplication_p.h (87%)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 326e5794b..8d44386e4 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 3e4cfa55a..f24b8d13d 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 674b57930..986de221f 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_);
@@ -577,10 +576,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 +601,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 +620,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 d056aca60..ffd0e30b7 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 2f9ff897b..0915a8492 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 708fb7fd9..55f666c1a 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();
 }
@@ -372,6 +380,56 @@ UserSettings::setCameraFrameRate(QString frameRate)
         save();
 }
 
+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()
 {
@@ -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 c699fd594..dd1e26d9a 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 5eeebb82a..58bdda349 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 9af388047..000000000
--- 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
similarity index 100%
rename from third_party/SingleApplication-3.1.3.1/.gitignore
rename to 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
similarity index 88%
rename from third_party/SingleApplication-3.1.3.1/CHANGELOG.md
rename to third_party/SingleApplication-3.2.0-dc8042b/CHANGELOG.md
index 3662b0b24..e2ba290e2 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
similarity index 53%
rename from third_party/SingleApplication-3.1.3.1/CMakeLists.txt
rename to third_party/SingleApplication-3.2.0-dc8042b/CMakeLists.txt
index 85dba84ce..ae1b1439b 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
similarity index 100%
rename from third_party/SingleApplication-3.1.3.1/LICENSE
rename to 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
similarity index 90%
rename from third_party/SingleApplication-3.1.3.1/README.md
rename to third_party/SingleApplication-3.2.0-dc8042b/README.md
index 3c36b5577..457ab3392 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
 =================
 [![CI](https://github.com/itay-grudev/SingleApplication/workflows/CI:%20Build%20Test/badge.svg)](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
similarity index 100%
rename from third_party/SingleApplication-3.1.3.1/SingleApplication
rename to 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
similarity index 100%
rename from third_party/SingleApplication-3.1.3.1/Windows.md
rename to 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 000000000..276ceee92
--- /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
similarity index 96%
rename from third_party/SingleApplication-3.1.3.1/singleapplication.h
rename to third_party/SingleApplication-3.2.0-dc8042b/singleapplication.h
index fd806a3db..d39a66143 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
similarity index 100%
rename from third_party/SingleApplication-3.1.3.1/singleapplication.pri
rename to 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
similarity index 67%
rename from third_party/SingleApplication-3.1.3.1/singleapplication_p.cpp
rename to third_party/SingleApplication-3.2.0-dc8042b/singleapplication_p.cpp
index 705609f2b..1ab58c239 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
similarity index 87%
rename from third_party/SingleApplication-3.1.3.1/singleapplication_p.h
rename to third_party/SingleApplication-3.2.0-dc8042b/singleapplication_p.h
index 29ba346ba..c49a46ddc 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();
-- 
GitLab