diff --git a/src/kleopatraapplication.cpp b/src/kleopatraapplication.cpp index 994b6374e..3a7bb18d0 100644 --- a/src/kleopatraapplication.cpp +++ b/src/kleopatraapplication.cpp @@ -1,831 +1,824 @@ /* kleopatraapplication.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "kleopatraapplication.h" #include "kleopatra_options.h" #include "mainwindow.h" #include "settings.h" #include "smimevalidationpreferences.h" #include "systrayicon.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "commands/checksumcreatefilescommand.h" #include "commands/checksumverifyfilescommand.h" #include "commands/decryptverifyfilescommand.h" #include "commands/detailscommand.h" #include "commands/importcertificatefromfilecommand.h" #include "commands/lookupcertificatescommand.h" #include "commands/newcertificatesigningrequestcommand.h" #include "commands/newopenpgpcertificatecommand.h" #include "commands/signencryptfilescommand.h" #include "dialogs/updatenotification.h" #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #if QT_CONFIG(graphicseffect) #include #endif #include #include #include #include #include #ifdef Q_OS_WIN #include #endif using namespace Kleo; using namespace Kleo::Commands; static void add_resources() { KIconLoader::global()->addAppDir(QStringLiteral("libkleopatra")); KIconLoader::global()->addAppDir(QStringLiteral("kwatchgnupg")); } static QList default_logging_options() { QList result; result.push_back("io"); return result; } namespace { class FocusFrame : public QFocusFrame { Q_OBJECT public: using QFocusFrame::QFocusFrame; protected: void paintEvent(QPaintEvent *event) override; }; static QRect effectiveWidgetRect(const QWidget *w) { // based on QWidgetPrivate::effectiveRectFor #if QT_CONFIG(graphicseffect) if (auto graphicsEffect = w->graphicsEffect(); graphicsEffect && graphicsEffect->isEnabled()) return graphicsEffect->boundingRectFor(w->rect()).toAlignedRect(); #endif // QT_CONFIG(graphicseffect) return w->rect(); } static QRect clipRect(const QWidget *w) { // based on QWidgetPrivate::clipRect if (!w->isVisible()) { return QRect(); } QRect r = effectiveWidgetRect(w); int ox = 0; int oy = 0; while (w && w->isVisible() && !w->isWindow() && w->parentWidget()) { ox -= w->x(); oy -= w->y(); w = w->parentWidget(); r &= QRect(ox, oy, w->width(), w->height()); } return r; } void FocusFrame::paintEvent(QPaintEvent *) { if (!widget()) { return; } QStylePainter p(this); QStyleOptionFocusRect option; initStyleOption(&option); const int vmargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &option); const int hmargin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &option); const QRect rect = clipRect(widget()).adjusted(0, 0, hmargin * 2, vmargin * 2); p.setClipRect(rect); p.drawPrimitive(QStyle::PE_FrameFocusRect, option); } } class KleopatraApplication::Private { friend class ::KleopatraApplication; KleopatraApplication *const q; public: explicit Private(KleopatraApplication *qq) : q(qq) , ignoreNewInstance(true) , firstNewInstance(true) , sysTray(nullptr) , groupConfig{std::make_shared(QStringLiteral("kleopatragroupsrc"))} { } ~Private() { #ifndef QT_NO_SYSTEMTRAYICON delete sysTray; #endif } void setUpSysTrayIcon() { #ifndef QT_NO_SYSTEMTRAYICON Q_ASSERT(readerStatus); sysTray = new SysTrayIcon(); sysTray->setFirstCardWithNullPin(readerStatus->firstCardWithNullPin()); sysTray->setAnyCardCanLearnKeys(readerStatus->anyCardCanLearnKeys()); connect(readerStatus.get(), &SmartCard::ReaderStatus::firstCardWithNullPinChanged, sysTray, &SysTrayIcon::setFirstCardWithNullPin); connect(readerStatus.get(), &SmartCard::ReaderStatus::anyCardCanLearnKeysChanged, sysTray, &SysTrayIcon::setAnyCardCanLearnKeys); #endif } private: void connectConfigureDialog() { if (configureDialog) { if (q->mainWindow()) { connect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted())); } connect(configureDialog, &ConfigureDialog::configCommitted, q, &KleopatraApplication::configurationChanged); } } void disconnectConfigureDialog() { if (configureDialog) { if (q->mainWindow()) { disconnect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted())); } disconnect(configureDialog, &ConfigureDialog::configCommitted, q, &KleopatraApplication::configurationChanged); } } public: bool ignoreNewInstance; bool firstNewInstance; QPointer focusFrame; QPointer configureDialog; QPointer mainWindow; std::unique_ptr readerStatus; #ifndef QT_NO_SYSTEMTRAYICON SysTrayIcon *sysTray; #endif std::shared_ptr groupConfig; std::shared_ptr keyCache; std::shared_ptr log; std::shared_ptr watcher; public: void setupKeyCache() { keyCache = KeyCache::mutableInstance(); keyCache->setRefreshInterval(SMimeValidationPreferences{}.refreshInterval()); watcher.reset(new FileSystemWatcher); watcher->whitelistFiles(gnupgFileWhitelist()); watcher->addPaths(gnupgFolderWhitelist()); watcher->setDelay(1000); keyCache->addFileSystemWatcher(watcher); keyCache->setGroupConfig(groupConfig); keyCache->setGroupsEnabled(Settings().groupsEnabled()); // always enable remarks (aka tags); in particular, this triggers a // relisting of the keys with signatures and signature notations // after the initial (fast) key listing keyCache->enableRemarks(true); } void setUpFilterManager() { if (!Settings{}.cmsEnabled()) { KeyFilterManager::instance()->alwaysFilterByProtocol(GpgME::OpenPGP); } } void setupLogging() { log = Log::mutableInstance(); const QByteArray envOptions = qgetenv("KLEOPATRA_LOGOPTIONS"); const bool logAll = envOptions.trimmed() == "all"; const QList options = envOptions.isEmpty() ? default_logging_options() : envOptions.split(','); const QByteArray dirNative = qgetenv("KLEOPATRA_LOGDIR"); if (dirNative.isEmpty()) { return; } const QString dir = QFile::decodeName(dirNative); const QString logFileName = QDir(dir).absoluteFilePath(QStringLiteral("kleopatra.log.%1").arg(QCoreApplication::applicationPid())); std::unique_ptr logFile(new QFile(logFileName)); if (!logFile->open(QIODevice::WriteOnly | QIODevice::Append)) { qCDebug(KLEOPATRA_LOG) << "Could not open file for logging: " << logFileName << "\nLogging disabled"; return; } log->setOutputDirectory(dir); if (logAll || options.contains("io")) { log->setIOLoggingEnabled(true); } qInstallMessageHandler(Log::messageHandler); if (logAll || options.contains("pipeio")) { KDPipeIODevice::setDebugLevel(KDPipeIODevice::Debug); } UiServer::setLogStream(log->logFile()); } void updateFocusFrame(QWidget *focusWidget) { if (focusWidget && focusWidget->inherits("QLabel") && focusWidget->window()->testAttribute(Qt::WA_KeyboardFocusChange)) { if (!focusFrame) { focusFrame = new FocusFrame{focusWidget}; } focusFrame->setWidget(focusWidget); } else if (focusFrame) { focusFrame->setWidget(nullptr); } } }; KleopatraApplication::KleopatraApplication(int &argc, char *argv[]) : QApplication(argc, argv) , d(new Private(this)) { // disable parent<->child navigation in tree views with left/right arrow keys // because this interferes with column by column navigation that is required // for accessibility setStyleSheet(QStringLiteral("QTreeView { arrow-keys-navigate-into-children: 0; }")); connect(this, &QApplication::focusChanged, this, [this](QWidget *, QWidget *now) { d->updateFocusFrame(now); }); } void KleopatraApplication::init() { #ifdef Q_OS_WIN QWindowsWindowFunctions::setWindowActivationBehavior(QWindowsWindowFunctions::AlwaysActivateWindow); #endif const auto blockedUrlSchemes = Settings{}.blockedUrlSchemes(); for (const auto &scheme : blockedUrlSchemes) { QDesktopServices::setUrlHandler(scheme, this, "blockUrl"); } add_resources(); DN::setAttributeOrder(Settings{}.attributeOrder()); /* Start the gpg-agent early, this is done explicitly * because on an empty keyring our keylistings wont start * the agent. In that case any assuan-connect calls to * the agent will fail. The requested start via the * connection is additionally done in case the gpg-agent * is killed while Kleopatra is running. */ startGpgAgent(); d->readerStatus.reset(new SmartCard::ReaderStatus); connect(d->readerStatus.get(), &SmartCard::ReaderStatus::startOfGpgAgentRequested, this, &KleopatraApplication::startGpgAgent); d->setupKeyCache(); d->setUpSysTrayIcon(); d->setUpFilterManager(); d->setupLogging(); -#ifdef Q_OS_WIN - // Under Linux the Theme is set outside. On Windows we have to do - // it ourself. - if (SystemInfo::isDarkModeActive()) { - QIcon::setThemeName(QStringLiteral("breeze-dark")); - } -#endif #ifndef QT_NO_SYSTEMTRAYICON d->sysTray->show(); #endif setQuitOnLastWindowClosed(false); // Sync config when we are about to quit connect(this, &QApplication::aboutToQuit, this, []() { KSharedConfig::openConfig()->sync(); }); } KleopatraApplication::~KleopatraApplication() { delete d->mainWindow; } namespace { using Func = void (KleopatraApplication::*)(const QStringList &, GpgME::Protocol); } void KleopatraApplication::slotActivateRequested(const QStringList &arguments, const QString &workingDirectory) { QCommandLineParser parser; kleopatra_options(&parser); QString err; if (!arguments.isEmpty() && !parser.parse(arguments)) { err = parser.errorText(); } else if (arguments.isEmpty()) { // KDBusServices omits the application name if no other // arguments are provided. In that case the parser prints // a warning. parser.parse(QStringList() << QCoreApplication::applicationFilePath()); } if (err.isEmpty()) { err = newInstance(parser, workingDirectory); } if (!err.isEmpty()) { KMessageBox::error(nullptr, err.toHtmlEscaped(), i18n("Failed to execute command")); Q_EMIT setExitValue(1); return; } Q_EMIT setExitValue(0); } QString KleopatraApplication::newInstance(const QCommandLineParser &parser, const QString &workingDirectory) { if (d->ignoreNewInstance) { qCDebug(KLEOPATRA_LOG) << "New instance ignored because of ignoreNewInstance"; return QString(); } QStringList files; const QDir cwd = QDir(workingDirectory); bool queryMode = parser.isSet(QStringLiteral("query")) || parser.isSet(QStringLiteral("search")); // Query and Search treat positional arguments differently, see below. if (!queryMode) { const auto positionalArguments = parser.positionalArguments(); for (const QString &file : positionalArguments) { // We do not check that file exists here. Better handle // these errors in the UI. if (QFileInfo(file).isAbsolute()) { files << file; } else { files << cwd.absoluteFilePath(file); } } } GpgME::Protocol protocol = GpgME::UnknownProtocol; if (parser.isSet(QStringLiteral("openpgp"))) { qCDebug(KLEOPATRA_LOG) << "found OpenPGP"; protocol = GpgME::OpenPGP; } if (parser.isSet(QStringLiteral("cms"))) { qCDebug(KLEOPATRA_LOG) << "found CMS"; if (protocol == GpgME::OpenPGP) { return i18n("Ambiguous protocol: --openpgp and --cms"); } protocol = GpgME::CMS; } // Check for Parent Window id WId parentId = 0; if (parser.isSet(QStringLiteral("parent-windowid"))) { #ifdef Q_OS_WIN // WId is not a portable type as it is a pointer type on Windows. // casting it from an integer is ok though as the values are guaranteed to // be compatible in the documentation. parentId = reinterpret_cast(parser.value(QStringLiteral("parent-windowid")).toUInt()); #else parentId = parser.value(QStringLiteral("parent-windowid")).toUInt(); #endif } // Handle openpgp4fpr URI scheme QString needle; if (queryMode) { needle = parser.positionalArguments().join(QLatin1Char(' ')); } if (needle.startsWith(QLatin1String("openpgp4fpr:"))) { needle.remove(0, 12); } // Check for --search command. if (parser.isSet(QStringLiteral("search"))) { // This is an extra command instead of a combination with the // similar query to avoid changing the older query commands behavior // and query's "show details if a certificate exist or search on a // keyserver" logic is hard to explain and use consistently. if (needle.isEmpty()) { return i18n("No search string specified for --search"); } auto const cmd = new LookupCertificatesCommand(needle, nullptr); cmd->setParentWId(parentId); cmd->start(); return QString(); } // Check for --query command if (parser.isSet(QStringLiteral("query"))) { if (needle.isEmpty()) { return i18n("No fingerprint argument specified for --query"); } auto cmd = Command::commandForQuery(needle); cmd->setParentWId(parentId); cmd->start(); return QString(); } // Check for --gen-key command if (parser.isSet(QStringLiteral("gen-key"))) { if (protocol == GpgME::CMS) { const Kleo::Settings settings{}; if (settings.cmsEnabled() && settings.cmsCertificateCreationAllowed()) { auto cmd = new NewCertificateSigningRequestCommand; cmd->setParentWId(parentId); cmd->start(); } else { return i18n("You are not allowed to create S/MIME certificate signing requests."); } } else { auto cmd = new NewOpenPGPCertificateCommand; cmd->setParentWId(parentId); cmd->start(); } return QString(); } // Check for --config command if (parser.isSet(QStringLiteral("config"))) { openConfigDialogWithForeignParent(parentId); return QString(); } struct FuncInfo { QString optionName; Func func; }; // While most of these options can be handled by the content autodetection // below it might be useful to override the autodetection if the input is in // doubt and you e.g. only want to import .asc files or fail and not decrypt them // if they are actually encrypted data. static const std::vector funcMap{ {QStringLiteral("import-certificate"), &KleopatraApplication::importCertificatesFromFile}, {QStringLiteral("encrypt"), &KleopatraApplication::encryptFiles}, {QStringLiteral("sign"), &KleopatraApplication::signFiles}, {QStringLiteral("encrypt-sign"), &KleopatraApplication::signEncryptFiles}, {QStringLiteral("sign-encrypt"), &KleopatraApplication::signEncryptFiles}, {QStringLiteral("decrypt"), &KleopatraApplication::decryptFiles}, {QStringLiteral("verify"), &KleopatraApplication::verifyFiles}, {QStringLiteral("decrypt-verify"), &KleopatraApplication::decryptVerifyFiles}, {QStringLiteral("checksum"), &KleopatraApplication::checksumFiles}, }; QString found; Func foundFunc = nullptr; for (const auto &[opt, fn] : funcMap) { if (parser.isSet(opt) && found.isEmpty()) { found = opt; foundFunc = fn; } else if (parser.isSet(opt)) { return i18n(R"(Ambiguous commands "%1" and "%2")", found, opt); } } QStringList errors; if (!found.isEmpty()) { if (files.empty()) { return i18n("No files specified for \"%1\" command", found); } qCDebug(KLEOPATRA_LOG) << "found" << found; (this->*foundFunc)(files, protocol); } else { if (files.empty()) { if (!(d->firstNewInstance && isSessionRestored())) { qCDebug(KLEOPATRA_LOG) << "openOrRaiseMainWindow"; openOrRaiseMainWindow(); } } else { for (const QString &fileName : std::as_const(files)) { QFileInfo fi(fileName); if (!fi.isReadable()) { errors << i18n("Cannot read \"%1\"", fileName); } } const QVector allCmds = Command::commandsForFiles(files); for (Command *cmd : allCmds) { if (parentId) { cmd->setParentWId(parentId); } else { MainWindow *mw = mainWindow(); if (!mw) { mw = new MainWindow; mw->setAttribute(Qt::WA_DeleteOnClose); setMainWindow(mw); d->connectConfigureDialog(); } cmd->setParentWidget(mw); } if (dynamic_cast(cmd)) { openOrRaiseMainWindow(); } cmd->start(); } } } d->firstNewInstance = false; #ifdef Q_OS_WIN // On Windows we might be started from the // explorer in any working directory. E.g. // a double click on a file. To avoid preventing // the folder from deletion we set the // working directory to the users homedir. QDir::setCurrent(QDir::homePath()); #endif return errors.join(QLatin1Char('\n')); } #ifndef QT_NO_SYSTEMTRAYICON const SysTrayIcon *KleopatraApplication::sysTrayIcon() const { return d->sysTray; } SysTrayIcon *KleopatraApplication::sysTrayIcon() { return d->sysTray; } #endif const MainWindow *KleopatraApplication::mainWindow() const { return d->mainWindow; } MainWindow *KleopatraApplication::mainWindow() { return d->mainWindow; } void KleopatraApplication::setMainWindow(MainWindow *mainWindow) { if (mainWindow == d->mainWindow) { return; } d->disconnectConfigureDialog(); d->mainWindow = mainWindow; #ifndef QT_NO_SYSTEMTRAYICON d->sysTray->setMainWindow(mainWindow); #endif d->connectConfigureDialog(); } static void open_or_raise(QWidget *w) { #ifdef Q_OS_WIN if (w->isMinimized()) { qCDebug(KLEOPATRA_LOG) << __func__ << "unminimizing and raising window"; w->raise(); } else if (w->isVisible()) { qCDebug(KLEOPATRA_LOG) << __func__ << "raising window"; w->raise(); #else if (w->isVisible()) { qCDebug(KLEOPATRA_LOG) << __func__ << "activating window"; KWindowSystem::updateStartupId(w->windowHandle()); KWindowSystem::activateWindow(w->windowHandle()); #endif } else { qCDebug(KLEOPATRA_LOG) << __func__ << "showing window"; w->show(); } } void KleopatraApplication::toggleMainWindowVisibility() { if (mainWindow()) { mainWindow()->setVisible(!mainWindow()->isVisible()); } else { openOrRaiseMainWindow(); } } void KleopatraApplication::restoreMainWindow() { qCDebug(KLEOPATRA_LOG) << "restoring main window"; // Sanity checks if (!isSessionRestored()) { qCDebug(KLEOPATRA_LOG) << "Not in session restore"; return; } if (mainWindow()) { qCDebug(KLEOPATRA_LOG) << "Already have main window"; return; } auto mw = new MainWindow; if (KMainWindow::canBeRestored(1)) { // restore to hidden state, Mainwindow::readProperties() will // restore saved visibility. mw->restore(1, false); } mw->setAttribute(Qt::WA_DeleteOnClose); setMainWindow(mw); d->connectConfigureDialog(); } void KleopatraApplication::openOrRaiseMainWindow() { MainWindow *mw = mainWindow(); if (!mw) { mw = new MainWindow; mw->setAttribute(Qt::WA_DeleteOnClose); setMainWindow(mw); d->connectConfigureDialog(); } open_or_raise(mw); UpdateNotification::checkUpdate(mw); } void KleopatraApplication::openConfigDialogWithForeignParent(WId parentWId) { if (!d->configureDialog) { d->configureDialog = new ConfigureDialog; d->configureDialog->setAttribute(Qt::WA_DeleteOnClose); d->connectConfigureDialog(); } // This is similar to what the commands do. if (parentWId) { if (QWidget *pw = QWidget::find(parentWId)) { d->configureDialog->setParent(pw, d->configureDialog->windowFlags()); } else { d->configureDialog->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(d->configureDialog->windowHandle(), parentWId); } } open_or_raise(d->configureDialog); // If we have a parent we want to raise over it. if (parentWId) { d->configureDialog->raise(); } } void KleopatraApplication::openOrRaiseConfigDialog() { openConfigDialogWithForeignParent(0); } #ifndef QT_NO_SYSTEMTRAYICON void KleopatraApplication::startMonitoringSmartCard() { Q_ASSERT(d->readerStatus); d->readerStatus->startMonitoring(); } #endif // QT_NO_SYSTEMTRAYICON void KleopatraApplication::importCertificatesFromFile(const QStringList &files, GpgME::Protocol /*proto*/) { openOrRaiseMainWindow(); if (!files.empty()) { mainWindow()->importCertificatesFromFile(files); } } void KleopatraApplication::encryptFiles(const QStringList &files, GpgME::Protocol proto) { auto const cmd = new SignEncryptFilesCommand(files, nullptr); cmd->setEncryptionPolicy(Force); cmd->setSigningPolicy(Allow); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::signFiles(const QStringList &files, GpgME::Protocol proto) { auto const cmd = new SignEncryptFilesCommand(files, nullptr); cmd->setSigningPolicy(Force); cmd->setEncryptionPolicy(Deny); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::signEncryptFiles(const QStringList &files, GpgME::Protocol proto) { auto const cmd = new SignEncryptFilesCommand(files, nullptr); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::decryptFiles(const QStringList &files, GpgME::Protocol /*proto*/) { auto const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->setOperation(Decrypt); cmd->start(); } void KleopatraApplication::verifyFiles(const QStringList &files, GpgME::Protocol /*proto*/) { auto const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->setOperation(Verify); cmd->start(); } void KleopatraApplication::decryptVerifyFiles(const QStringList &files, GpgME::Protocol /*proto*/) { auto const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->start(); } void KleopatraApplication::checksumFiles(const QStringList &files, GpgME::Protocol /*proto*/) { QStringList verifyFiles, createFiles; for (const QString &file : files) { if (isChecksumFile(file)) { verifyFiles << file; } else { createFiles << file; } } if (!verifyFiles.isEmpty()) { auto const cmd = new ChecksumVerifyFilesCommand(verifyFiles, nullptr); cmd->start(); } if (!createFiles.isEmpty()) { auto const cmd = new ChecksumCreateFilesCommand(createFiles, nullptr); cmd->start(); } } void KleopatraApplication::setIgnoreNewInstance(bool ignore) { d->ignoreNewInstance = ignore; } bool KleopatraApplication::ignoreNewInstance() const { return d->ignoreNewInstance; } void KleopatraApplication::blockUrl(const QUrl &url) { qCDebug(KLEOPATRA_LOG) << "Blocking URL" << url; KMessageBox::error(mainWindow(), i18n("Opening an external link is administratively prohibited."), i18n("Prohibited")); } void KleopatraApplication::startGpgAgent() { Kleo::launchGpgAgent(); } #include "kleopatraapplication.moc" diff --git a/src/main.cpp b/src/main.cpp index 3aaec3797..90e65b2c7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,301 +1,306 @@ /* main.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2001, 2002, 2004, 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "aboutdata.h" #include "kleopatraapplication.h" #include "mainwindow.h" #include "accessibility/accessiblewidgetfactory.h" #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include #endif #include #include #include "utils/kuniqueservice.h" #include "utils/userinfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include "kleopatra_options.h" #include #include #include #include #include #include #include #include // for Qt::escape #include #include #include #include #include #include #include #include QElapsedTimer startupTimer; static bool selfCheck() { Kleo::Commands::SelfTestCommand cmd(nullptr); cmd.setAutoDelete(false); cmd.setAutomaticMode(true); QEventLoop loop; QObject::connect(&cmd, &Kleo::Commands::SelfTestCommand::finished, &loop, &QEventLoop::quit); QTimer::singleShot(0, &cmd, &Kleo::Command::start); // start() may Q_EMIT finished()... loop.exec(); if (cmd.isCanceled()) { return false; } else { return true; } } static void fillKeyCache(Kleo::UiServer *server) { auto cmd = new Kleo::ReloadKeysCommand(nullptr); QObject::connect(cmd, SIGNAL(finished()), server, SLOT(enableCryptoCommands())); cmd->start(); } int main(int argc, char **argv) { startupTimer.start(); +#ifdef Q_OS_WIN + // note: requires https://invent.kde.org/frameworks/kiconthemes/-/merge_requests/109 + qputenv("QT_QPA_PLATFORM", "windows:darkmode=2"); +#endif + // Qt::AA_EnableHighDpiScaling must be set before QGuiApplication is constructed. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); #endif KleopatraApplication app(argc, argv); // Set OrganizationDomain early as this is used to generate the service // name that will be registered on the bus. app.setOrganizationDomain(QStringLiteral("kde.org")); STARTUP_TIMING << "Application created"; /* Create the unique service ASAP to prevent double starts if * the application is started twice very quickly. */ KUniqueService service; QObject::connect(&service, &KUniqueService::activateRequested, &app, &KleopatraApplication::slotActivateRequested); QObject::connect(&app, &KleopatraApplication::setExitValue, &service, [&service](int i) { service.setExitValue(i); }); STARTUP_TIMING << "Service created"; KCrash::initialize(); QAccessible::installFactory(Kleo::accessibleWidgetFactory); qCDebug(KLEOPATRA_LOG) << "Application created"; app.setWindowIcon(QIcon::fromTheme(QStringLiteral("kleopatra"), app.windowIcon())); KLocalizedString::setApplicationDomain("kleopatra"); // Initialize GpgME { const GpgME::Error gpgmeInitError = GpgME::initializeLibrary(0); if (gpgmeInitError) { KMessageBox::error(nullptr, xi18nc("@info", "The version of the GpgME library you are running against " "is older than the one that the GpgME++ library was built against." "Kleopatra will not function in this setting." "Please ask your administrator for help in resolving this issue."), i18nc("@title", "GpgME Too Old")); return EXIT_FAILURE; } STARTUP_TIMING << "GPGME Initialized"; } AboutData aboutData; KAboutData::setApplicationData(aboutData); /* This is more expensive as it sounds as it might run a verification * on a signed Version file and initializes the whole GpgME::Engine. */ STARTUP_TIMING << "Versions checked"; if (Kleo::userIsElevated()) { /* This is a safeguard against bugreports that something fails because * of permission problems on windows. Some users still have the Windows * Vista behavior of running things as Administrator. This can break * GnuPG in horrible ways for example if a stale lockfile is left that * can't be removed without another elevation. * * Note: This is not the same as running as root on Linux. Elevated means * that you are temporarily running with the "normal" user environment but * with elevated permissions. * */ if (KMessageBox::warningContinueCancel(nullptr, xi18nc("@info", "Kleopatra cannot be run as adminstrator without " "breaking file permissions in the GnuPG data folder." "To manage keys for other users please manage them as a normal user and " "copy the AppData\\Roaming\\gnupg directory with proper permissions.") + xi18n("Are you sure that you want to continue?"), i18nc("@title", "Running as Administrator")) != KMessageBox::Continue) { return EXIT_FAILURE; } qCWarning(KLEOPATRA_LOG) << "User is running with administrative permissions."; } // Delay init after KUniqueservice call as this might already // have terminated us and so we can avoid overhead (e.g. keycache // setup / systray icon). app.init(); STARTUP_TIMING << "Application initialized"; QCommandLineParser parser; aboutData.setupCommandLine(&parser); kleopatra_options(&parser); parser.process(QApplication::arguments()); aboutData.processCommandLine(&parser); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) Kdelibs4ConfigMigrator migrate(QStringLiteral("kleopatra")); migrate.setConfigFiles(QStringList() << QStringLiteral("kleopatrarc") << QStringLiteral("libkleopatrarc")); migrate.setUiFiles(QStringList() << QStringLiteral("kleopatra.rc")); migrate.migrate(); #endif { const unsigned int threads = QThreadPool::globalInstance()->maxThreadCount(); QThreadPool::globalInstance()->setMaxThreadCount(qMax(2U, threads)); } Kleo::ChecksumDefinition::setInstallPath(Kleo::gpg4winInstallPath()); Kleo::ArchiveDefinition::setInstallPath(Kleo::gnupgInstallPath()); #ifndef DISABLE_UISERVER int rc; Kleo::UiServer *server = nullptr; try { server = new Kleo::UiServer(parser.value(QStringLiteral("uiserver-socket"))); STARTUP_TIMING << "UiServer created"; QObject::connect(server, &Kleo::UiServer::startKeyManagerRequested, &app, &KleopatraApplication::openOrRaiseMainWindow); QObject::connect(server, &Kleo::UiServer::startConfigDialogRequested, &app, &KleopatraApplication::openOrRaiseConfigDialog); #define REGISTER(Command) server->registerCommandFactory(std::shared_ptr(new Kleo::GenericAssuanCommandFactory)) REGISTER(CreateChecksumsCommand); REGISTER(DecryptCommand); REGISTER(DecryptFilesCommand); REGISTER(DecryptVerifyFilesCommand); REGISTER(EchoCommand); REGISTER(EncryptCommand); REGISTER(EncryptFilesCommand); REGISTER(EncryptSignFilesCommand); REGISTER(ImportFilesCommand); REGISTER(PrepEncryptCommand); REGISTER(PrepSignCommand); REGISTER(SelectCertificateCommand); REGISTER(SignCommand); REGISTER(SignEncryptFilesCommand); REGISTER(SignFilesCommand); REGISTER(VerifyChecksumsCommand); REGISTER(VerifyCommand); REGISTER(VerifyFilesCommand); #undef REGISTER server->start(); STARTUP_TIMING << "UiServer started"; } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Failed to start UI Server: " << e.what(); #ifdef Q_OS_WIN // We should probably change the UIServer to be only run on Windows at all because // only the Windows Explorer Plugin uses it. But the plan of GnuPG devs as of 2022 is to // change the Windows Explorer Plugin to use the command line and then remove the // UiServer for everyone. QMessageBox::information(nullptr, i18n("GPG UI Server Error"), i18nc("This error message is only shown on Windows when the socket to communicate with " "Windows Explorer could not be created. This often times means that the whole installation is " "buggy. e.g. GnuPG is not installed at all.", "The Kleopatra Windows Explorer Module could not be initialized.
" "The error given was: %1
" "This likely means that there is a problem with your installation. Try reinstalling or " "contact your Administrator for support.
" "You can try to continue to use Kleopatra but there might be other problems.
", QString::fromUtf8(e.what()).toHtmlEscaped())); #endif } #endif // DISABLE_UISERVER const bool daemon = parser.isSet(QStringLiteral("daemon")); if (!daemon && app.isSessionRestored()) { app.restoreMainWindow(); } if (!selfCheck()) { return EXIT_FAILURE; } STARTUP_TIMING << "SelfCheck completed"; if (server) { fillKeyCache(server); } #ifndef QT_NO_SYSTEMTRAYICON app.startMonitoringSmartCard(); #endif app.setIgnoreNewInstance(false); if (!daemon) { const QString err = app.newInstance(parser); if (!err.isEmpty()) { std::cerr << i18n("Invalid arguments: %1", err).toLocal8Bit().constData() << "\n"; return EXIT_FAILURE; } STARTUP_TIMING << "new instance created"; } rc = app.exec(); app.setIgnoreNewInstance(true); QObject::disconnect(server, &Kleo::UiServer::startKeyManagerRequested, &app, &KleopatraApplication::openOrRaiseMainWindow); QObject::disconnect(server, &Kleo::UiServer::startConfigDialogRequested, &app, &KleopatraApplication::openOrRaiseConfigDialog); if (server) { server->stop(); server->waitForStopped(); delete server; } return rc; }