Switch profile code to a more flexible method
This introduces a new version of SingleApplication as well.
This commit is contained in:
parent
100b5e0371
commit
53f45bdb1c
21 changed files with 654 additions and 373 deletions
|
|
@ -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()
|
||||
{
|
||||
UserSettings settings;
|
||||
|
||||
nhlog::db()->debug("setting up cache");
|
||||
|
||||
auto statePath = QString("%1/%2")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
|
||||
.arg(QString::fromUtf8(localUserId_.toUtf8().toHex()));
|
||||
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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_);
|
||||
|
||||
settings.endGroup();
|
||||
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(); // user
|
||||
|
||||
settings.sync();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
64
src/main.cpp
64
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]() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue