From 4ba8ef30f2a62e488046a0c700f97534a1da7471 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 29 Apr 2020 17:47:25 +0200 Subject: [PATCH] Add option to launch KeePassXC at system startup Fixes #1218 --- src/gui/ApplicationSettingsWidget.cpp | 7 +++ src/gui/ApplicationSettingsWidgetGeneral.ui | 9 +++- src/gui/osutils/OSUtilsBase.h | 19 ++++++- src/gui/osutils/macutils/MacUtils.cpp | 40 ++++++++++++-- src/gui/osutils/macutils/MacUtils.h | 6 ++- src/gui/osutils/nixutils/NixUtils.cpp | 59 ++++++++++++++++++++- src/gui/osutils/nixutils/NixUtils.h | 6 ++- src/gui/osutils/winutils/WinUtils.cpp | 19 ++++++- src/gui/osutils/winutils/WinUtils.h | 4 +- src/main.cpp | 8 +-- 10 files changed, 160 insertions(+), 17 deletions(-) diff --git a/src/gui/ApplicationSettingsWidget.cpp b/src/gui/ApplicationSettingsWidget.cpp index 7f4c10ce3..4dbe40697 100644 --- a/src/gui/ApplicationSettingsWidget.cpp +++ b/src/gui/ApplicationSettingsWidget.cpp @@ -27,6 +27,7 @@ #include "core/Global.h" #include "core/Resources.h" #include "core/Translator.h" +#include "gui/osutils/OSUtils.h" #include "MessageBox.h" #include "touchid/TouchID.h" @@ -173,8 +174,10 @@ void ApplicationSettingsWidget::loadSettings() #ifdef QT_DEBUG m_generalUi->singleInstanceCheckBox->setEnabled(false); + m_generalUi->launchAtStartup->setEnabled(false); #endif m_generalUi->singleInstanceCheckBox->setChecked(config()->get(Config::SingleInstance).toBool()); + m_generalUi->launchAtStartup->setChecked(osUtils->isLaunchAtStartupEnabled()); m_generalUi->rememberLastDatabasesCheckBox->setChecked(config()->get(Config::RememberLastDatabases).toBool()); m_generalUi->rememberLastKeyFilesCheckBox->setChecked(config()->get(Config::RememberLastKeyFiles).toBool()); m_generalUi->openPreviousDatabasesOnStartupCheckBox->setChecked( @@ -299,6 +302,10 @@ void ApplicationSettingsWidget::saveSettings() return; } +#ifndef QT_DEBUG + osUtils->setLaunchAtStartup(m_generalUi->launchAtStartup->isChecked()); +#endif + config()->set(Config::SingleInstance, m_generalUi->singleInstanceCheckBox->isChecked()); config()->set(Config::RememberLastDatabases, m_generalUi->rememberLastDatabasesCheckBox->isChecked()); config()->set(Config::RememberLastKeyFiles, m_generalUi->rememberLastKeyFilesCheckBox->isChecked()); diff --git a/src/gui/ApplicationSettingsWidgetGeneral.ui b/src/gui/ApplicationSettingsWidgetGeneral.ui index 7a04217ec..55cb28ac5 100644 --- a/src/gui/ApplicationSettingsWidgetGeneral.ui +++ b/src/gui/ApplicationSettingsWidgetGeneral.ui @@ -6,8 +6,6 @@ 0 0 - 499 - 1174 @@ -49,6 +47,13 @@ + + + + Automatically launch KeePassXC at system startup + + + diff --git a/src/gui/osutils/OSUtilsBase.h b/src/gui/osutils/OSUtilsBase.h index 0606d3226..340e9bf7e 100644 --- a/src/gui/osutils/OSUtilsBase.h +++ b/src/gui/osutils/OSUtilsBase.h @@ -30,7 +30,24 @@ class OSUtilsBase : public QObject Q_OBJECT public: - virtual bool isDarkMode() = 0; + /** + * @return OS dark mode enabled. + */ + virtual bool isDarkMode() const = 0; + + /** + * @return KeePassXC set to launch at system startup (autostart). + */ + virtual bool isLaunchAtStartupEnabled() const = 0; + + /** + * @param enable Add or remove KeePassXC from system autostart. + */ + virtual void setLaunchAtStartup(bool enable) = 0; + + /** + * @return OS caps lock enabled. + */ virtual bool isCapslockEnabled() = 0; protected: diff --git a/src/gui/osutils/macutils/MacUtils.cpp b/src/gui/osutils/macutils/MacUtils.cpp index b888b18e1..9203d73f2 100644 --- a/src/gui/osutils/macutils/MacUtils.cpp +++ b/src/gui/osutils/macutils/MacUtils.cpp @@ -18,6 +18,10 @@ #include "MacUtils.h" #include +#include +#include +#include +#include #include @@ -74,11 +78,6 @@ bool MacUtils::isHidden() return m_appkit->isHidden(m_appkit->ownProcessId()); } -bool MacUtils::isDarkMode() -{ - return m_appkit->isDarkMode(); -} - bool MacUtils::enableAccessibility() { return m_appkit->enableAccessibility(); @@ -89,6 +88,37 @@ bool MacUtils::enableScreenRecording() return m_appkit->enableScreenRecording(); } +bool MacUtils::isDarkMode() const +{ + return m_appkit->isDarkMode(); +} + +QString MacUtils::getLaunchAgentFilename() const +{ + auto launchAgentDir = QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/../LaunchAgents")); + return QFile(launchAgentDir.absoluteFilePath( + qApp->property("KPXC_QUALIFIED_APPNAME").toString().append(".plist"))).fileName(); +} + +bool MacUtils::isLaunchAtStartupEnabled() const +{ + return QFile::exists(getLaunchAgentFilename()); +} + +void MacUtils::setLaunchAtStartup(bool enable) +{ + if (enable) { + QSettings agent(getLaunchAgentFilename(), QSettings::NativeFormat); + agent.setValue("Label", qApp->property("KPXC_QUALIFIED_APPNAME").toString()); + agent.setValue("ProgramArguments", QStringList() << QApplication::applicationFilePath()); + agent.setValue("RunAtLoad", true); + agent.setValue("StandardErrorPath", "/dev/null"); + agent.setValue("StandardOutPath", "/dev/null"); + } else if (isLaunchAtStartupEnabled()) { + QFile::remove(getLaunchAgentFilename()); + } +} + bool MacUtils::isCapslockEnabled() { return (CGEventSourceFlagsState(kCGEventSourceStateHIDSystemState) & kCGEventFlagMaskAlphaShift) != 0; diff --git a/src/gui/osutils/macutils/MacUtils.h b/src/gui/osutils/macutils/MacUtils.h index b2180c30a..ccabf6788 100644 --- a/src/gui/osutils/macutils/MacUtils.h +++ b/src/gui/osutils/macutils/MacUtils.h @@ -33,7 +33,9 @@ class MacUtils : public OSUtilsBase public: static MacUtils* instance(); - bool isDarkMode() override; + bool isDarkMode() const override; + bool isLaunchAtStartupEnabled() const override; + void setLaunchAtStartup(bool enable) override; bool isCapslockEnabled() override; WId activeWindow(); @@ -53,6 +55,8 @@ protected: ~MacUtils() override; private: + QString getLaunchAgentFilename() const; + QScopedPointer m_appkit; static QPointer m_instance; diff --git a/src/gui/osutils/nixutils/NixUtils.cpp b/src/gui/osutils/nixutils/NixUtils.cpp index e4de7c9c3..b252458e5 100644 --- a/src/gui/osutils/nixutils/NixUtils.cpp +++ b/src/gui/osutils/nixutils/NixUtils.cpp @@ -16,11 +16,16 @@ */ #include "NixUtils.h" + #include #include +#include +#include #include #include +#include #include +#include #include // namespace required to avoid name clashes with declarations in XKBlib.h @@ -49,7 +54,7 @@ NixUtils::~NixUtils() { } -bool NixUtils::isDarkMode() +bool NixUtils::isDarkMode() const { if (!qApp || !qApp->style()) { return false; @@ -57,6 +62,58 @@ bool NixUtils::isDarkMode() return qApp->style()->standardPalette().color(QPalette::Window).toHsl().lightness() < 110; } +QString NixUtils::getAutostartDesktopFilename(bool createDirs) const +{ + QDir autostartDir; + auto confHome = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); + if (confHome.isEmpty()) { + return {}; + } + autostartDir.setPath(confHome + QStringLiteral("/autostart")); + if (createDirs && !autostartDir.exists()) { + autostartDir.mkpath("."); + } + + return QFile(autostartDir.absoluteFilePath(qApp->property("KPXC_QUALIFIED_APPNAME").toString().append(".desktop"))) + .fileName(); +} + +bool NixUtils::isLaunchAtStartupEnabled() const +{ + return QFile::exists(getAutostartDesktopFilename()); + ; +} + +void NixUtils::setLaunchAtStartup(bool enable) +{ + if (enable) { + QFile desktopFile(getAutostartDesktopFilename(true)); + if (!desktopFile.open(QIODevice::WriteOnly)) { + qWarning("Failed to create autostart desktop file."); + return; + } + QTextStream stream(&desktopFile); + stream.setCodec("UTF-8"); + stream << QStringLiteral("[Desktop Entry]") << '\n' + << QStringLiteral("Name=") << QApplication::applicationDisplayName() << '\n' + << QStringLiteral("GenericName=") << tr("Password Manager") << '\n' + << QStringLiteral("Exec=") << QApplication::applicationFilePath() << '\n' + << QStringLiteral("TryExec=") << QApplication::applicationFilePath() << '\n' + << QStringLiteral("Icon=") << QApplication::applicationName().toLower() << '\n' + << QStringLiteral("StartupWMClass=keepassxc") << '\n' + << QStringLiteral("StartupNotify=true") << '\n' + << QStringLiteral("Terminal=false") << '\n' + << QStringLiteral("Type=Application") << '\n' + << QStringLiteral("Version=1.0") << "true" << '\n' + << QStringLiteral("Categories=Utility;Security;Qt;") << '\n' + << QStringLiteral("MimeType=application/x-keepass2;") << '\n' + << QStringLiteral("X-GNOME-Autostart-enabled=true") << endl; + desktopFile.close(); + } else if (isLaunchAtStartupEnabled()) { + QFile::remove(getAutostartDesktopFilename()); + } +} + bool NixUtils::isCapslockEnabled() { QPlatformNativeInterface* native = QGuiApplication::platformNativeInterface(); diff --git a/src/gui/osutils/nixutils/NixUtils.h b/src/gui/osutils/nixutils/NixUtils.h index b23cf143d..c91580796 100644 --- a/src/gui/osutils/nixutils/NixUtils.h +++ b/src/gui/osutils/nixutils/NixUtils.h @@ -28,7 +28,9 @@ class NixUtils : public OSUtilsBase public: static NixUtils* instance(); - bool isDarkMode() override; + bool isDarkMode() const override; + bool isLaunchAtStartupEnabled() const override; + void setLaunchAtStartup(bool enable) override; bool isCapslockEnabled() override; private: @@ -36,6 +38,8 @@ private: ~NixUtils() override; private: + QString getAutostartDesktopFilename(bool createDirs = false) const; + static QPointer m_instance; Q_DISABLE_COPY(NixUtils) diff --git a/src/gui/osutils/winutils/WinUtils.cpp b/src/gui/osutils/winutils/WinUtils.cpp index f3cd9e8a8..44f77043e 100644 --- a/src/gui/osutils/winutils/WinUtils.cpp +++ b/src/gui/osutils/winutils/WinUtils.cpp @@ -77,13 +77,30 @@ bool WinUtils::DWMEventFilter::nativeEventFilter(const QByteArray& eventType, vo return false; } -bool WinUtils::isDarkMode() +bool WinUtils::isDarkMode() const { QSettings settings(R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)", QSettings::NativeFormat); return settings.value("AppsUseLightTheme", 1).toInt() == 0; } +bool WinUtils::isLaunchAtStartupEnabled() const +{ + return QSettings(R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run)", QSettings::NativeFormat) + .contains(qAppName()); + ; +} + +void WinUtils::setLaunchAtStartup(bool enable) +{ + QSettings reg(R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run)", QSettings::NativeFormat); + if (enable) { + reg.setValue(qAppName(), QApplication::applicationFilePath()); + } else { + reg.remove(qAppName()); + } +} + bool WinUtils::isCapslockEnabled() { return GetKeyState(VK_CAPITAL) == 1; diff --git a/src/gui/osutils/winutils/WinUtils.h b/src/gui/osutils/winutils/WinUtils.h index 0965074fa..bf49f2c7f 100644 --- a/src/gui/osutils/winutils/WinUtils.h +++ b/src/gui/osutils/winutils/WinUtils.h @@ -32,7 +32,9 @@ public: static WinUtils* instance(); static void registerEventFilters(); - bool isDarkMode() override; + bool isDarkMode() const override; + bool isLaunchAtStartupEnabled() const override; + void setLaunchAtStartup(bool enable) override; bool isCapslockEnabled() override; protected: diff --git a/src/main.cpp b/src/main.cpp index c3494f02f..89ea235ad 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -53,13 +53,13 @@ int main(int argc, char** argv) QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif -#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) - QGuiApplication::setDesktopFileName("org.keepassxc.KeePassXC.desktop"); -#endif - Application app(argc, argv); Application::setApplicationName("KeePassXC"); Application::setApplicationVersion(KEEPASSXC_VERSION); + app.setProperty("KPXC_QUALIFIED_APPNAME", "org.keepassxc.KeePassXC"); +#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)