From 9886b1075fbddca0d4ef564c1bb481afcc199c3f Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sun, 20 Sep 2020 09:39:59 -0400 Subject: [PATCH] Cleanup config initialization, add local config options * Fix #5313, allow specifying local config path using environment variable and command line flag * Add command line flag `--localconfig ` to specify a file path to use for the local configuration settings. * Add environment variable support to set config files paths: `KPXC_CONFIG` and `KPXC_CONFIG_LOCAL` to override default locations. * Reorder startup sequence to load specified config files earlier to allow for theme settings and other early options to be picked up. * Removed old command line option `--pw`, no longer used. * Attempt a fix of application not closing when last window is gone. Only set `QApplication::setQuitOnLastWindowClosed(true)` when tray icon is enabled instead of always. --- docs/styles/dark.css | 1 + docs/topics/DownloadInstall.adoc | 2 + docs/topics/UserInterface.adoc | 37 +++++++++++++ src/core/Config.cpp | 90 ++++++++++++++++++-------------- src/core/Config.h | 7 +-- src/gui/MainWindow.cpp | 4 ++ src/main.cpp | 53 +++++++++---------- 7 files changed, 125 insertions(+), 69 deletions(-) diff --git a/docs/styles/dark.css b/docs/styles/dark.css index a32c366f3f..8f7bd67b6d 100644 --- a/docs/styles/dark.css +++ b/docs/styles/dark.css @@ -455,6 +455,7 @@ p{font-family: "Noto Sans",sans-serif !important} blockquote{color:var(--quotecolor) !important} .quoteblock{color:var(--textcolor)} code{color:var(--textcoloralt);background-color: var(--sidebarbackground) !important} +pre,pre>code{line-height:1.25; color:var(--textcoloralt);} .keyseq{color:var(--textcoloralt);} diff --git a/docs/topics/DownloadInstall.adoc b/docs/topics/DownloadInstall.adoc index 4e17c66bc6..cc6f1fcb7b 100644 --- a/docs/topics/DownloadInstall.adoc +++ b/docs/topics/DownloadInstall.adoc @@ -51,6 +51,8 @@ image::linux_store.png[] The Snap and Flatpak options are sandboxed applications (more secure). The Native option is installed with the operating system files. Read more about the limitations of these options here: https://keepassxc.org/docs/#faq-appsnap-yubikey[KeePassXC Snap FAQ] +NOTE: KeePassXC stores a configuration file in `~/.cache` to remember window position, recent files, and other local settings. If you mount this folder to a tmpdisk you will lose settings after reboot. + === macOS To install the KeePassXC app on macOS, double click on the downloaded DMG file and use the click and drag option as shown: diff --git a/docs/topics/UserInterface.adoc b/docs/topics/UserInterface.adoc index 1fee946084..1f0ca9cf20 100644 --- a/docs/topics/UserInterface.adoc +++ b/docs/topics/UserInterface.adoc @@ -48,4 +48,41 @@ image::compact_mode_comparison.png[] === Keyboard Shortcuts include::KeyboardShortcuts.adoc[tag=content, leveloffset=+1] + +// tag::advanced[] +=== Command-Line Options +You can use the following command line options to tailor the application to your preferences: + +---- +Usage: keepassxc.exe [options] [filename(s)] +KeePassXC - cross-platform password manager + +Options: + -?, -h, --help Displays help on commandline options. + --help-all Displays help including Qt specific options. + -v, --version Displays version information. + --config path to a custom config file + --localconfig path to a custom local config file + --keyfile key file of the database + --pw-stdin read password of the database from stdin + --debug-info Displays debugging information. + +Arguments: + filename(s) filenames of the password databases to open (*.kdbx) +---- + +Additionally, the following environment variables may be useful when running the application: + +[grid=rows, frame=none, width=75%] +|=== +|Env Var | Description + +|KPXC_CONFIG | Override default path to roaming configuration file +|KPXC_CONFIG_LOCAL | Override default path to local configuration file +|SSH_AUTH_SOCKET | Path of the unix file socket that the agent uses for communication with other processes (SSH Agent) +|QT_SCALE_FACTOR [numeric] | Defines a global scale factor for the whole application, including point-sized fonts. +|QT_SCREEN_SCALE_FACTORS [list] | Specifies scale factors for each screen. See https://doc.qt.io/qt-5/highdpi.html#high-dpi-support-in-qt +|QT_SCALE_FACTOR_ROUNDING_POLICY | Control device pixel ratio rounding to the nearest integer. See https://doc.qt.io/qt-5/highdpi.html#high-dpi-support-in-qt +|=== +// end::advanced[] // end::content[] diff --git a/src/core/Config.cpp b/src/core/Config.cpp index afb71f534b..58ddd9ae49 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -418,49 +419,17 @@ void Config::migrate() sync(); } -Config::Config(const QString& fileName, QObject* parent) +Config::Config(const QString& configFileName, const QString& localConfigFileName, QObject* parent) : QObject(parent) { - init(fileName); + init(configFileName, localConfigFileName); } Config::Config(QObject* parent) : QObject(parent) { - // Check if we are running in portable mode, if so store the config files local to the app - auto portablePath = QCoreApplication::applicationDirPath().append("/%1"); - if (QFile::exists(portablePath.arg(".portable"))) { - init(portablePath.arg("config/keepassxc.ini"), portablePath.arg("config/keepassxc_local.ini")); - return; - } - - QString configPath; - QString localConfigPath; - -#if defined(Q_OS_WIN) - configPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - localConfigPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); -#elif defined(Q_OS_MACOS) - configPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - localConfigPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); -#else - // On case-sensitive Operating Systems, force use of lowercase app directories - configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/keepassxc"; - localConfigPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/keepassxc"; -#endif - - configPath += "/keepassxc"; - localConfigPath += "/keepassxc"; - -#ifdef QT_DEBUG - configPath += "_debug"; - localConfigPath += "_debug"; -#endif - - configPath += ".ini"; - localConfigPath += ".ini"; - - init(QDir::toNativeSeparators(configPath), QDir::toNativeSeparators(localConfigPath)); + auto configFiles = defaultConfigFiles(); + init(configFiles.first, configFiles.second); } Config::~Config() @@ -488,6 +457,45 @@ void Config::init(const QString& configFileName, const QString& localConfigFileN connect(qApp, &QCoreApplication::aboutToQuit, this, &Config::sync); } +QPair Config::defaultConfigFiles() +{ + // Check if we are running in portable mode, if so store the config files local to the app + auto portablePath = QCoreApplication::applicationDirPath().append("/%1"); + if (QFile::exists(portablePath.arg(".portable"))) { + return {portablePath.arg("config/keepassxc.ini"), portablePath.arg("config/keepassxc_local.ini")}; + } + + QString configPath; + QString localConfigPath; + +#if defined(Q_OS_WIN) + configPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + localConfigPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); +#elif defined(Q_OS_MACOS) + configPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + localConfigPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); +#else + // On case-sensitive Operating Systems, force use of lowercase app directories + configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/keepassxc"; + localConfigPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/keepassxc"; +#endif + + QString suffix; +#ifdef QT_DEBUG + suffix = "_debug"; +#endif + + configPath += QString("/keepassxc%1.ini").arg(suffix); + localConfigPath += QString("/keepassxc%1.ini").arg(suffix); + + // Allow overriding the default location with env vars + const auto& env = QProcessEnvironment::systemEnvironment(); + configPath = env.value("KPXC_CONFIG", configPath); + localConfigPath = env.value("KPXC_CONFIG_LOCAL", localConfigPath); + + return {QDir::toNativeSeparators(configPath), QDir::toNativeSeparators(localConfigPath)}; +} + Config* Config::instance() { if (!m_instance) { @@ -497,12 +505,16 @@ Config* Config::instance() return m_instance; } -void Config::createConfigFromFile(const QString& file) +void Config::createConfigFromFile(const QString& configFileName, const QString& localConfigFileName) { if (m_instance) { delete m_instance; } - m_instance = new Config(file, qApp); + + auto defaultFiles = defaultConfigFiles(); + m_instance = new Config(configFileName.isEmpty() ? defaultFiles.first : configFileName, + localConfigFileName.isEmpty() ? defaultFiles.second : localConfigFileName, + qApp); } void Config::createTempFileInstance() @@ -514,7 +526,7 @@ void Config::createTempFileInstance() bool openResult = tmpFile->open(); Q_ASSERT(openResult); Q_UNUSED(openResult); - m_instance = new Config(tmpFile->fileName(), qApp); + m_instance = new Config(tmpFile->fileName(), "", qApp); tmpFile->setParent(m_instance); } diff --git a/src/core/Config.h b/src/core/Config.h index 64e8c945a5..2ed4c6ec11 100644 --- a/src/core/Config.h +++ b/src/core/Config.h @@ -198,17 +198,18 @@ class Config : public QObject void resetToDefaults(); static Config* instance(); - static void createConfigFromFile(const QString& file); + static void createConfigFromFile(const QString& configFileName, const QString& localConfigFileName = {}); static void createTempFileInstance(); signals: void changed(ConfigKey key); private: - Config(const QString& fileName, QObject* parent = nullptr); + Config(const QString& configFileName, const QString& localConfigFileName, QObject* parent); explicit Config(QObject* parent); - void init(const QString& configFileName, const QString& localConfigFileName = ""); + void init(const QString& configFileName, const QString& localConfigFileName); void migrate(); + static QPair defaultConfigFiles(); static QPointer m_instance; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 81bbf3a083..f5e0541b39 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -1269,6 +1269,8 @@ bool MainWindow::saveLastDatabases() void MainWindow::updateTrayIcon() { if (isTrayIconEnabled()) { + QApplication::setQuitOnLastWindowClosed(false); + if (!m_trayIcon) { m_trayIcon = new QSystemTrayIcon(this); auto* menu = new QMenu(this); @@ -1307,6 +1309,8 @@ void MainWindow::updateTrayIcon() m_trayIcon->setIcon(resources()->trayIconLocked()); } } else { + QApplication::setQuitOnLastWindowClosed(true); + if (m_trayIcon) { m_trayIcon->hide(); delete m_trayIcon; diff --git a/src/main.cpp b/src/main.cpp index 7e340da4da..b88dc41e02 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -56,51 +56,47 @@ int main(int argc, char** argv) QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); #endif - Application app(argc, argv); - Application::setApplicationName("KeePassXC"); - Application::setApplicationVersion(KEEPASSXC_VERSION); - app.setProperty("KPXC_QUALIFIED_APPNAME", "org.keepassxc.KeePassXC"); - app.applyTheme(); -#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) - QGuiApplication::setDesktopFileName(app.property("KPXC_QUALIFIED_APPNAME").toString() + QStringLiteral(".desktop")); -#endif - - // don't set organizationName as that changes the return value of - // QStandardPaths::writableLocation(QDesktopServices::DataLocation) - Bootstrap::bootstrapApplication(); - QCommandLineParser parser; parser.setApplicationDescription(QObject::tr("KeePassXC - cross-platform password manager")); parser.addPositionalArgument( - "filename", QObject::tr("filenames of the password databases to open (*.kdbx)"), "[filename(s)]"); + "filename(s)", QObject::tr("filenames of the password databases to open (*.kdbx)"), "[filename(s)]"); QCommandLineOption configOption("config", QObject::tr("path to a custom config file"), "config"); + QCommandLineOption localConfigOption( + "localconfig", QObject::tr("path to a custom local config file"), "localconfig"); QCommandLineOption keyfileOption("keyfile", QObject::tr("key file of the database"), "keyfile"); QCommandLineOption pwstdinOption("pw-stdin", QObject::tr("read password of the database from stdin")); - // This is needed under Windows where clients send --parent-window parameter with Native Messaging connect method - QCommandLineOption parentWindowOption(QStringList() << "pw" - << "parent-window", - QObject::tr("Parent window handle"), - "handle"); QCommandLineOption helpOption = parser.addHelpOption(); QCommandLineOption versionOption = parser.addVersionOption(); QCommandLineOption debugInfoOption(QStringList() << "debug-info", QObject::tr("Displays debugging information.")); parser.addOption(configOption); + parser.addOption(localConfigOption); parser.addOption(keyfileOption); parser.addOption(pwstdinOption); - parser.addOption(parentWindowOption); parser.addOption(debugInfoOption); + Application app(argc, argv); + // don't set organizationName as that changes the return value of + // QStandardPaths::writableLocation(QDesktopServices::DataLocation) + Application::setApplicationName("KeePassXC"); + Application::setApplicationVersion(KEEPASSXC_VERSION); + app.setProperty("KPXC_QUALIFIED_APPNAME", "org.keepassxc.KeePassXC"); + parser.process(app); - // Don't try and do anything with the application if we're only showing the help / version + // Exit early if we're only showing the help / version if (parser.isSet(versionOption) || parser.isSet(helpOption)) { return EXIT_SUCCESS; } - const QStringList fileNames = parser.positionalArguments(); + // Process config file options early + if (parser.isSet(configOption) || parser.isSet(localConfigOption)) { + Config::createConfigFromFile(parser.value(configOption), parser.value(localConfigOption)); + } + // Process single instance and early exit if already running + const QStringList fileNames = parser.positionalArguments(); if (app.isAlreadyRunning()) { if (!fileNames.isEmpty()) { app.sendFileNamesToRunningInstance(fileNames); @@ -109,7 +105,14 @@ int main(int argc, char** argv) return EXIT_SUCCESS; } - QApplication::setQuitOnLastWindowClosed(false); + // Apply the configured theme before creating any GUI elements + app.applyTheme(); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) + QGuiApplication::setDesktopFileName(app.property("KPXC_QUALIFIED_APPNAME").toString() + QStringLiteral(".desktop")); +#endif + + Bootstrap::bootstrapApplication(); if (!Crypto::init()) { QString error = QObject::tr("Fatal error while testing the cryptographic functions."); @@ -128,10 +131,6 @@ int main(int argc, char** argv) return EXIT_SUCCESS; } - if (parser.isSet(configOption)) { - Config::createConfigFromFile(parser.value(configOption)); - } - MainWindow mainWindow; QObject::connect(&app, SIGNAL(anotherInstanceStarted()), &mainWindow, SLOT(bringToFront())); QObject::connect(&app, SIGNAL(applicationActivated()), &mainWindow, SLOT(bringToFront()));