File indexing completed on 2024-05-19 05:20:17

0001 /*
0002     kleopatraapplication.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
0008     SPDX-FileContributor: Intevation GmbH
0009 
0010     SPDX-License-Identifier: GPL-2.0-or-later
0011 */
0012 
0013 #include <config-kleopatra.h>
0014 
0015 #include "kleopatraapplication.h"
0016 
0017 #include "kleopatra_options.h"
0018 #include "mainwindow.h"
0019 #include "settings.h"
0020 #include "smimevalidationpreferences.h"
0021 #include "systrayicon.h"
0022 
0023 #include <conf/configuredialog.h>
0024 #include <conf/groupsconfigdialog.h>
0025 #include <smartcard/readerstatus.h>
0026 
0027 #include <Libkleo/GnuPG>
0028 #include <utils/kdpipeiodevice.h>
0029 #include <utils/log.h>
0030 
0031 #include <gpgme++/key.h>
0032 
0033 #include <Libkleo/Classify>
0034 #include <Libkleo/Dn>
0035 #include <Libkleo/FileSystemWatcher>
0036 #include <Libkleo/KeyCache>
0037 #include <Libkleo/KeyFilterManager>
0038 #include <Libkleo/KeyGroupConfig>
0039 #include <Libkleo/SystemInfo>
0040 
0041 #include <uiserver/uiserver.h>
0042 
0043 #include "commands/checksumcreatefilescommand.h"
0044 #include "commands/checksumverifyfilescommand.h"
0045 #include "commands/decryptverifyfilescommand.h"
0046 #include "commands/detailscommand.h"
0047 #include "commands/importcertificatefromfilecommand.h"
0048 #include "commands/lookupcertificatescommand.h"
0049 #include "commands/newcertificatesigningrequestcommand.h"
0050 #include "commands/newopenpgpcertificatecommand.h"
0051 #include "commands/signencryptfilescommand.h"
0052 
0053 #include "dialogs/updatenotification.h"
0054 
0055 #include "kleopatra_debug.h"
0056 #include <KColorSchemeManager>
0057 #include <KIconLoader>
0058 #include <KLocalizedString>
0059 #include <KMessageBox>
0060 #include <KWindowSystem>
0061 
0062 #include <QDesktopServices>
0063 #include <QDir>
0064 #include <QFile>
0065 #include <QFocusFrame>
0066 #if QT_CONFIG(graphicseffect)
0067 #include <QGraphicsEffect>
0068 #endif
0069 #include <QPointer>
0070 #include <QSettings>
0071 #include <QStyleOption>
0072 #include <QStylePainter>
0073 
0074 #include <KSharedConfig>
0075 #include <memory>
0076 
0077 #ifdef Q_OS_WIN
0078 #include <QtPlatformHeaders/QWindowsWindowFunctions>
0079 #endif
0080 
0081 using namespace Kleo;
0082 using namespace Kleo::Commands;
0083 
0084 static void add_resources()
0085 {
0086     KIconLoader::global()->addAppDir(QStringLiteral("libkleopatra"));
0087     KIconLoader::global()->addAppDir(QStringLiteral("kwatchgnupg"));
0088 }
0089 
0090 static QList<QByteArray> default_logging_options()
0091 {
0092     QList<QByteArray> result;
0093     result.push_back("io");
0094     return result;
0095 }
0096 
0097 namespace
0098 {
0099 class FocusFrame : public QFocusFrame
0100 {
0101     Q_OBJECT
0102 public:
0103     using QFocusFrame::QFocusFrame;
0104 
0105 protected:
0106     void paintEvent(QPaintEvent *event) override;
0107 };
0108 
0109 static QRect effectiveWidgetRect(const QWidget *w)
0110 {
0111     // based on QWidgetPrivate::effectiveRectFor
0112 #if QT_CONFIG(graphicseffect)
0113     if (auto graphicsEffect = w->graphicsEffect(); graphicsEffect && graphicsEffect->isEnabled())
0114         return graphicsEffect->boundingRectFor(w->rect()).toAlignedRect();
0115 #endif // QT_CONFIG(graphicseffect)
0116     return w->rect();
0117 }
0118 
0119 static QRect clipRect(const QWidget *w)
0120 {
0121     // based on QWidgetPrivate::clipRect
0122     if (!w->isVisible()) {
0123         return QRect();
0124     }
0125     QRect r = effectiveWidgetRect(w);
0126     int ox = 0;
0127     int oy = 0;
0128     while (w && w->isVisible() && !w->isWindow() && w->parentWidget()) {
0129         ox -= w->x();
0130         oy -= w->y();
0131         w = w->parentWidget();
0132         r &= QRect(ox, oy, w->width(), w->height());
0133     }
0134     return r;
0135 }
0136 
0137 void FocusFrame::paintEvent(QPaintEvent *)
0138 {
0139     if (!widget()) {
0140         return;
0141     }
0142 
0143     QStylePainter p(this);
0144     QStyleOptionFocusRect option;
0145     initStyleOption(&option);
0146     const int vmargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &option);
0147     const int hmargin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &option);
0148     const QRect rect = clipRect(widget()).adjusted(0, 0, hmargin * 2, vmargin * 2);
0149     p.setClipRect(rect);
0150     p.drawPrimitive(QStyle::PE_FrameFocusRect, option);
0151 }
0152 }
0153 
0154 class KleopatraApplication::Private
0155 {
0156     friend class ::KleopatraApplication;
0157     KleopatraApplication *const q;
0158 
0159 public:
0160     explicit Private(KleopatraApplication *qq)
0161         : q(qq)
0162         , ignoreNewInstance(true)
0163         , firstNewInstance(true)
0164         , sysTray(nullptr)
0165         , groupConfig{std::make_shared<KeyGroupConfig>(QStringLiteral("kleopatragroupsrc"))}
0166     {
0167     }
0168     ~Private()
0169     {
0170 #ifndef QT_NO_SYSTEMTRAYICON
0171         delete sysTray;
0172 #endif
0173     }
0174     void setUpSysTrayIcon()
0175     {
0176 #ifndef QT_NO_SYSTEMTRAYICON
0177         Q_ASSERT(readerStatus);
0178         sysTray = new SysTrayIcon();
0179         sysTray->setFirstCardWithNullPin(readerStatus->firstCardWithNullPin());
0180         sysTray->setAnyCardCanLearnKeys(readerStatus->anyCardCanLearnKeys());
0181 
0182         connect(readerStatus.get(), &SmartCard::ReaderStatus::firstCardWithNullPinChanged, sysTray, &SysTrayIcon::setFirstCardWithNullPin);
0183         connect(readerStatus.get(), &SmartCard::ReaderStatus::anyCardCanLearnKeysChanged, sysTray, &SysTrayIcon::setAnyCardCanLearnKeys);
0184 #endif
0185     }
0186 
0187 private:
0188     void connectConfigureDialog()
0189     {
0190         if (configureDialog) {
0191             if (q->mainWindow()) {
0192                 connect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted()));
0193             }
0194             connect(configureDialog, &ConfigureDialog::configCommitted, q, &KleopatraApplication::configurationChanged);
0195         }
0196     }
0197     void disconnectConfigureDialog()
0198     {
0199         if (configureDialog) {
0200             if (q->mainWindow()) {
0201                 disconnect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted()));
0202             }
0203             disconnect(configureDialog, &ConfigureDialog::configCommitted, q, &KleopatraApplication::configurationChanged);
0204         }
0205     }
0206 
0207 public:
0208     bool ignoreNewInstance;
0209     bool firstNewInstance;
0210     QPointer<FocusFrame> focusFrame;
0211     QPointer<ConfigureDialog> configureDialog;
0212     QPointer<GroupsConfigDialog> groupsConfigDialog;
0213     QPointer<MainWindow> mainWindow;
0214     std::unique_ptr<SmartCard::ReaderStatus> readerStatus;
0215 #ifndef QT_NO_SYSTEMTRAYICON
0216     SysTrayIcon *sysTray;
0217 #endif
0218     std::shared_ptr<KeyGroupConfig> groupConfig;
0219     std::shared_ptr<KeyCache> keyCache;
0220     std::shared_ptr<Log> log;
0221     std::shared_ptr<FileSystemWatcher> watcher;
0222     std::shared_ptr<QSettings> distroSettings;
0223 
0224 public:
0225     void setupKeyCache()
0226     {
0227         keyCache = KeyCache::mutableInstance();
0228         keyCache->setRefreshInterval(SMimeValidationPreferences{}.refreshInterval());
0229         watcher.reset(new FileSystemWatcher);
0230 
0231         watcher->whitelistFiles(gnupgFileWhitelist());
0232         watcher->addPaths(gnupgFolderWhitelist());
0233         watcher->setDelay(1000);
0234         keyCache->addFileSystemWatcher(watcher);
0235         keyCache->setGroupConfig(groupConfig);
0236         keyCache->setGroupsEnabled(Settings().groupsEnabled());
0237         // always enable remarks (aka tags); in particular, this triggers a
0238         // relisting of the keys with signatures and signature notations
0239         // after the initial (fast) key listing
0240         keyCache->enableRemarks(true);
0241     }
0242 
0243     void setUpFilterManager()
0244     {
0245         if (!Settings{}.cmsEnabled()) {
0246             KeyFilterManager::instance()->alwaysFilterByProtocol(GpgME::OpenPGP);
0247         }
0248     }
0249 
0250     void setupLogging()
0251     {
0252         log = Log::mutableInstance();
0253 
0254         const QByteArray envOptions = qgetenv("KLEOPATRA_LOGOPTIONS");
0255         const bool logAll = envOptions.trimmed() == "all";
0256         const QList<QByteArray> options = envOptions.isEmpty() ? default_logging_options() : envOptions.split(',');
0257 
0258         const QByteArray dirNative = qgetenv("KLEOPATRA_LOGDIR");
0259         if (dirNative.isEmpty()) {
0260             return;
0261         }
0262         const QString dir = QFile::decodeName(dirNative);
0263         const QString logFileName = QDir(dir).absoluteFilePath(QStringLiteral("kleopatra.log.%1").arg(QCoreApplication::applicationPid()));
0264         std::unique_ptr<QFile> logFile(new QFile(logFileName));
0265         if (!logFile->open(QIODevice::WriteOnly | QIODevice::Append)) {
0266             qCDebug(KLEOPATRA_LOG) << "Could not open file for logging: " << logFileName << "\nLogging disabled";
0267             return;
0268         }
0269 
0270         log->setOutputDirectory(dir);
0271         if (logAll || options.contains("io")) {
0272             log->setIOLoggingEnabled(true);
0273         }
0274         qInstallMessageHandler(Log::messageHandler);
0275 
0276         if (logAll || options.contains("pipeio")) {
0277             KDPipeIODevice::setDebugLevel(KDPipeIODevice::Debug);
0278         }
0279         UiServer::setLogStream(log->logFile());
0280     }
0281 
0282     void updateFocusFrame(QWidget *focusWidget)
0283     {
0284         if (focusWidget && focusWidget->inherits("QLabel") && focusWidget->window()->testAttribute(Qt::WA_KeyboardFocusChange)) {
0285             if (!focusFrame) {
0286                 focusFrame = new FocusFrame{focusWidget};
0287             }
0288             focusFrame->setWidget(focusWidget);
0289         } else if (focusFrame) {
0290             focusFrame->setWidget(nullptr);
0291         }
0292     }
0293 };
0294 
0295 KleopatraApplication::KleopatraApplication(int &argc, char *argv[])
0296     : QApplication(argc, argv)
0297     , d(new Private(this))
0298 {
0299     // disable parent<->child navigation in tree views with left/right arrow keys
0300     // because this interferes with column by column navigation that is required
0301     // for accessibility
0302     setStyleSheet(QStringLiteral("QTreeView { arrow-keys-navigate-into-children: 0; }"));
0303     connect(this, &QApplication::focusChanged, this, [this](QWidget *, QWidget *now) {
0304         d->updateFocusFrame(now);
0305     });
0306 }
0307 
0308 void KleopatraApplication::init()
0309 {
0310 #ifdef Q_OS_WIN
0311     QWindowsWindowFunctions::setWindowActivationBehavior(QWindowsWindowFunctions::AlwaysActivateWindow);
0312 #endif
0313     const auto blockedUrlSchemes = Settings{}.blockedUrlSchemes();
0314     for (const auto &scheme : blockedUrlSchemes) {
0315         QDesktopServices::setUrlHandler(scheme, this, "blockUrl");
0316     }
0317     add_resources();
0318     DN::setAttributeOrder(Settings{}.attributeOrder());
0319     /* Start the gpg-agent early, this is done explicitly
0320      * because on an empty keyring our keylistings wont start
0321      * the agent. In that case any assuan-connect calls to
0322      * the agent will fail. The requested start via the
0323      * connection is additionally done in case the gpg-agent
0324      * is killed while Kleopatra is running. */
0325     startGpgAgent();
0326     d->readerStatus.reset(new SmartCard::ReaderStatus);
0327     connect(d->readerStatus.get(), &SmartCard::ReaderStatus::startOfGpgAgentRequested, this, &KleopatraApplication::startGpgAgent);
0328     d->setupKeyCache();
0329     d->setUpSysTrayIcon();
0330     d->setUpFilterManager();
0331     d->setupLogging();
0332 #ifdef Q_OS_WIN
0333     if (!SystemInfo::isHighContrastModeActive()) {
0334         /* In high contrast mode we do not want our own colors */
0335         new KColorSchemeManager(this);
0336     }
0337 #else
0338     new KColorSchemeManager(this);
0339 #endif
0340 
0341 #ifndef QT_NO_SYSTEMTRAYICON
0342     d->sysTray->show();
0343 #endif
0344     setQuitOnLastWindowClosed(false);
0345 
0346     // Sync config when we are about to quit
0347     connect(this, &QApplication::aboutToQuit, this, []() {
0348         KSharedConfig::openConfig()->sync();
0349     });
0350 }
0351 
0352 KleopatraApplication::~KleopatraApplication()
0353 {
0354     delete d->groupsConfigDialog;
0355     delete d->mainWindow;
0356 }
0357 
0358 namespace
0359 {
0360 using Func = void (KleopatraApplication::*)(const QStringList &, GpgME::Protocol);
0361 }
0362 
0363 void KleopatraApplication::slotActivateRequested(const QStringList &arguments, const QString &workingDirectory)
0364 {
0365     QCommandLineParser parser;
0366 
0367     kleopatra_options(&parser);
0368     QString err;
0369     if (!arguments.isEmpty() && !parser.parse(arguments)) {
0370         err = parser.errorText();
0371     } else if (arguments.isEmpty()) {
0372         // KDBusServices omits the application name if no other
0373         // arguments are provided. In that case the parser prints
0374         // a warning.
0375         parser.parse(QStringList() << QCoreApplication::applicationFilePath());
0376     }
0377 
0378     if (err.isEmpty()) {
0379         err = newInstance(parser, workingDirectory);
0380     }
0381 
0382     if (!err.isEmpty()) {
0383         KMessageBox::error(nullptr, err.toHtmlEscaped(), i18n("Failed to execute command"));
0384         Q_EMIT setExitValue(1);
0385         return;
0386     }
0387     Q_EMIT setExitValue(0);
0388 }
0389 
0390 QString KleopatraApplication::newInstance(const QCommandLineParser &parser, const QString &workingDirectory)
0391 {
0392     if (d->ignoreNewInstance) {
0393         qCDebug(KLEOPATRA_LOG) << "New instance ignored because of ignoreNewInstance";
0394         return QString();
0395     }
0396 
0397     QStringList files;
0398     const QDir cwd = QDir(workingDirectory);
0399     bool queryMode = parser.isSet(QStringLiteral("query")) || parser.isSet(QStringLiteral("search"));
0400 
0401     // Query and Search treat positional arguments differently, see below.
0402     if (!queryMode) {
0403         const auto positionalArguments = parser.positionalArguments();
0404         for (const QString &file : positionalArguments) {
0405             // We do not check that file exists here. Better handle
0406             // these errors in the UI.
0407             if (QFileInfo(file).isAbsolute()) {
0408                 files << file;
0409             } else {
0410                 files << cwd.absoluteFilePath(file);
0411             }
0412         }
0413     }
0414 
0415     GpgME::Protocol protocol = GpgME::UnknownProtocol;
0416 
0417     if (parser.isSet(QStringLiteral("openpgp"))) {
0418         qCDebug(KLEOPATRA_LOG) << "found OpenPGP";
0419         protocol = GpgME::OpenPGP;
0420     }
0421 
0422     if (parser.isSet(QStringLiteral("cms"))) {
0423         qCDebug(KLEOPATRA_LOG) << "found CMS";
0424         if (protocol == GpgME::OpenPGP) {
0425             return i18n("Ambiguous protocol: --openpgp and --cms");
0426         }
0427         protocol = GpgME::CMS;
0428     }
0429 
0430     // Check for Parent Window id
0431     WId parentId = 0;
0432     if (parser.isSet(QStringLiteral("parent-windowid"))) {
0433 #ifdef Q_OS_WIN
0434         // WId is not a portable type as it is a pointer type on Windows.
0435         // casting it from an integer is ok though as the values are guaranteed to
0436         // be compatible in the documentation.
0437         parentId = reinterpret_cast<WId>(parser.value(QStringLiteral("parent-windowid")).toUInt());
0438 #else
0439         parentId = parser.value(QStringLiteral("parent-windowid")).toUInt();
0440 #endif
0441     }
0442 
0443     // Handle openpgp4fpr URI scheme
0444     QString needle;
0445     if (queryMode) {
0446         needle = parser.positionalArguments().join(QLatin1Char(' '));
0447     }
0448     if (needle.startsWith(QLatin1StringView("openpgp4fpr:"))) {
0449         needle.remove(0, 12);
0450     }
0451 
0452     // Check for --search command.
0453     if (parser.isSet(QStringLiteral("search"))) {
0454         // This is an extra command instead of a combination with the
0455         // similar query to avoid changing the older query commands behavior
0456         // and query's "show details if a certificate exist or search on a
0457         // keyserver" logic is hard to explain and use consistently.
0458         if (needle.isEmpty()) {
0459             return i18n("No search string specified for --search");
0460         }
0461         auto const cmd = new LookupCertificatesCommand(needle, nullptr);
0462         cmd->setParentWId(parentId);
0463         cmd->start();
0464         return QString();
0465     }
0466 
0467     // Check for --query command
0468     if (parser.isSet(QStringLiteral("query"))) {
0469         if (needle.isEmpty()) {
0470             return i18n("No fingerprint argument specified for --query");
0471         }
0472         auto cmd = Command::commandForQuery(needle);
0473         cmd->setParentWId(parentId);
0474         cmd->start();
0475         return QString();
0476     }
0477 
0478     // Check for --gen-key command
0479     if (parser.isSet(QStringLiteral("gen-key"))) {
0480         if (protocol == GpgME::CMS) {
0481             const Kleo::Settings settings{};
0482             if (settings.cmsEnabled() && settings.cmsCertificateCreationAllowed()) {
0483                 auto cmd = new NewCertificateSigningRequestCommand;
0484                 cmd->setParentWId(parentId);
0485                 cmd->start();
0486             } else {
0487                 return i18n("You are not allowed to create S/MIME certificate signing requests.");
0488             }
0489         } else {
0490             auto cmd = new NewOpenPGPCertificateCommand;
0491             cmd->setParentWId(parentId);
0492             cmd->start();
0493         }
0494         return QString();
0495     }
0496 
0497     // Check for --config command
0498     if (parser.isSet(QStringLiteral("config"))) {
0499         openConfigDialogWithForeignParent(parentId);
0500         return QString();
0501     }
0502 
0503     struct FuncInfo {
0504         QString optionName;
0505         Func func;
0506     };
0507 
0508     // While most of these options can be handled by the content autodetection
0509     // below it might be useful to override the autodetection if the input is in
0510     // doubt and you e.g. only want to import .asc files or fail and not decrypt them
0511     // if they are actually encrypted data.
0512     static const std::vector<FuncInfo> funcMap{
0513         {QStringLiteral("import-certificate"), &KleopatraApplication::importCertificatesFromFile},
0514         {QStringLiteral("encrypt"), &KleopatraApplication::encryptFiles},
0515         {QStringLiteral("sign"), &KleopatraApplication::signFiles},
0516         {QStringLiteral("encrypt-sign"), &KleopatraApplication::signEncryptFiles},
0517         {QStringLiteral("sign-encrypt"), &KleopatraApplication::signEncryptFiles},
0518         {QStringLiteral("decrypt"), &KleopatraApplication::decryptFiles},
0519         {QStringLiteral("verify"), &KleopatraApplication::verifyFiles},
0520         {QStringLiteral("decrypt-verify"), &KleopatraApplication::decryptVerifyFiles},
0521         {QStringLiteral("checksum"), &KleopatraApplication::checksumFiles},
0522     };
0523 
0524     QString found;
0525     Func foundFunc = nullptr;
0526     for (const auto &[opt, fn] : funcMap) {
0527         if (parser.isSet(opt) && found.isEmpty()) {
0528             found = opt;
0529             foundFunc = fn;
0530         } else if (parser.isSet(opt)) {
0531             return i18n(R"(Ambiguous commands "%1" and "%2")", found, opt);
0532         }
0533     }
0534 
0535     QStringList errors;
0536     if (!found.isEmpty()) {
0537         if (files.empty()) {
0538             return i18n("No files specified for \"%1\" command", found);
0539         }
0540         qCDebug(KLEOPATRA_LOG) << "found" << found;
0541         (this->*foundFunc)(files, protocol);
0542     } else {
0543         if (files.empty()) {
0544             if (!(d->firstNewInstance && isSessionRestored())) {
0545                 qCDebug(KLEOPATRA_LOG) << "openOrRaiseMainWindow";
0546                 openOrRaiseMainWindow();
0547             }
0548         } else {
0549             for (const QString &fileName : std::as_const(files)) {
0550                 QFileInfo fi(fileName);
0551                 if (!fi.isReadable()) {
0552                     errors << i18n("Cannot read \"%1\"", fileName);
0553                 }
0554             }
0555             handleFiles(files, parentId);
0556         }
0557     }
0558     d->firstNewInstance = false;
0559 
0560 #ifdef Q_OS_WIN
0561     // On Windows we might be started from the
0562     // explorer in any working directory. E.g.
0563     // a double click on a file. To avoid preventing
0564     // the folder from deletion we set the
0565     // working directory to the users homedir.
0566     QDir::setCurrent(QDir::homePath());
0567 #endif
0568 
0569     return errors.join(QLatin1Char('\n'));
0570 }
0571 
0572 void KleopatraApplication::handleFiles(const QStringList &files, WId parentId)
0573 {
0574     const QList<Command *> allCmds = Command::commandsForFiles(files);
0575     for (Command *cmd : allCmds) {
0576         if (parentId) {
0577             cmd->setParentWId(parentId);
0578         } else {
0579             MainWindow *mw = mainWindow();
0580             if (!mw) {
0581                 mw = new MainWindow;
0582                 mw->setAttribute(Qt::WA_DeleteOnClose);
0583                 setMainWindow(mw);
0584                 d->connectConfigureDialog();
0585             }
0586             cmd->setParentWidget(mw);
0587         }
0588         if (dynamic_cast<ImportCertificateFromFileCommand *>(cmd)) {
0589             openOrRaiseMainWindow();
0590         }
0591         cmd->start();
0592     }
0593 }
0594 
0595 #ifndef QT_NO_SYSTEMTRAYICON
0596 const SysTrayIcon *KleopatraApplication::sysTrayIcon() const
0597 {
0598     return d->sysTray;
0599 }
0600 
0601 SysTrayIcon *KleopatraApplication::sysTrayIcon()
0602 {
0603     return d->sysTray;
0604 }
0605 #endif
0606 
0607 const MainWindow *KleopatraApplication::mainWindow() const
0608 {
0609     return d->mainWindow;
0610 }
0611 
0612 MainWindow *KleopatraApplication::mainWindow()
0613 {
0614     return d->mainWindow;
0615 }
0616 
0617 void KleopatraApplication::setMainWindow(MainWindow *mainWindow)
0618 {
0619     if (mainWindow == d->mainWindow) {
0620         return;
0621     }
0622 
0623     d->disconnectConfigureDialog();
0624 
0625     d->mainWindow = mainWindow;
0626 #ifndef QT_NO_SYSTEMTRAYICON
0627     d->sysTray->setMainWindow(mainWindow);
0628 #endif
0629 
0630     d->connectConfigureDialog();
0631 }
0632 
0633 static void open_or_raise(QWidget *w)
0634 {
0635 #ifdef Q_OS_WIN
0636     if (w->isMinimized()) {
0637         qCDebug(KLEOPATRA_LOG) << __func__ << "unminimizing and raising window";
0638         w->raise();
0639     } else if (w->isVisible()) {
0640         qCDebug(KLEOPATRA_LOG) << __func__ << "raising window";
0641         w->raise();
0642 #else
0643     if (w->isVisible()) {
0644         qCDebug(KLEOPATRA_LOG) << __func__ << "activating window";
0645         KWindowSystem::updateStartupId(w->windowHandle());
0646         KWindowSystem::activateWindow(w->windowHandle());
0647 #endif
0648     } else {
0649         qCDebug(KLEOPATRA_LOG) << __func__ << "showing window";
0650         w->show();
0651     }
0652 }
0653 
0654 void KleopatraApplication::toggleMainWindowVisibility()
0655 {
0656     if (mainWindow()) {
0657         mainWindow()->setVisible(!mainWindow()->isVisible());
0658     } else {
0659         openOrRaiseMainWindow();
0660     }
0661     if (mainWindow()->isVisible()) {
0662         mainWindow()->exportWindow();
0663     } else {
0664         mainWindow()->unexportWindow();
0665     }
0666 }
0667 
0668 void KleopatraApplication::restoreMainWindow()
0669 {
0670     qCDebug(KLEOPATRA_LOG) << "restoring main window";
0671 
0672     // Sanity checks
0673     if (!isSessionRestored()) {
0674         qCDebug(KLEOPATRA_LOG) << "Not in session restore";
0675         return;
0676     }
0677 
0678     if (mainWindow()) {
0679         qCDebug(KLEOPATRA_LOG) << "Already have main window";
0680         return;
0681     }
0682 
0683     auto mw = new MainWindow;
0684     if (KMainWindow::canBeRestored(1)) {
0685         // restore to hidden state, Mainwindow::readProperties() will
0686         // restore saved visibility.
0687         mw->restore(1, false);
0688     }
0689 
0690     mw->setAttribute(Qt::WA_DeleteOnClose);
0691     setMainWindow(mw);
0692     d->connectConfigureDialog();
0693 }
0694 
0695 void KleopatraApplication::openOrRaiseMainWindow()
0696 {
0697     MainWindow *mw = mainWindow();
0698     if (!mw) {
0699         mw = new MainWindow;
0700         mw->setAttribute(Qt::WA_DeleteOnClose);
0701         setMainWindow(mw);
0702         d->connectConfigureDialog();
0703     }
0704     open_or_raise(mw);
0705     UpdateNotification::checkUpdate(mw);
0706 }
0707 
0708 void KleopatraApplication::openConfigDialogWithForeignParent(WId parentWId)
0709 {
0710     if (!d->configureDialog) {
0711         d->configureDialog = new ConfigureDialog;
0712         d->configureDialog->setAttribute(Qt::WA_DeleteOnClose);
0713         d->connectConfigureDialog();
0714     }
0715 
0716     // This is similar to what the commands do.
0717     if (parentWId) {
0718         if (QWidget *pw = QWidget::find(parentWId)) {
0719             d->configureDialog->setParent(pw, d->configureDialog->windowFlags());
0720         } else {
0721             d->configureDialog->setAttribute(Qt::WA_NativeWindow, true);
0722             KWindowSystem::setMainWindow(d->configureDialog->windowHandle(), parentWId);
0723         }
0724     }
0725 
0726     open_or_raise(d->configureDialog);
0727 
0728     // If we have a parent we want to raise over it.
0729     if (parentWId) {
0730         d->configureDialog->raise();
0731     }
0732 }
0733 
0734 void KleopatraApplication::openOrRaiseConfigDialog()
0735 {
0736     openConfigDialogWithForeignParent(0);
0737 }
0738 
0739 void KleopatraApplication::openOrRaiseGroupsConfigDialog(QWidget *parent)
0740 {
0741     if (!d->groupsConfigDialog) {
0742         d->groupsConfigDialog = new GroupsConfigDialog{parent};
0743         d->groupsConfigDialog->setAttribute(Qt::WA_DeleteOnClose);
0744     } else {
0745         // reparent the dialog to ensure it's shown on top of the (modal) parent
0746         d->groupsConfigDialog->setParent(parent, Qt::Dialog);
0747     }
0748     open_or_raise(d->groupsConfigDialog);
0749 }
0750 
0751 #ifndef QT_NO_SYSTEMTRAYICON
0752 void KleopatraApplication::startMonitoringSmartCard()
0753 {
0754     Q_ASSERT(d->readerStatus);
0755     d->readerStatus->startMonitoring();
0756 }
0757 #endif // QT_NO_SYSTEMTRAYICON
0758 
0759 void KleopatraApplication::importCertificatesFromFile(const QStringList &files, GpgME::Protocol /*proto*/)
0760 {
0761     openOrRaiseMainWindow();
0762     if (!files.empty()) {
0763         mainWindow()->importCertificatesFromFile(files);
0764     }
0765 }
0766 
0767 void KleopatraApplication::encryptFiles(const QStringList &files, GpgME::Protocol proto)
0768 {
0769     auto const cmd = new SignEncryptFilesCommand(files, nullptr);
0770     cmd->setEncryptionPolicy(Force);
0771     cmd->setSigningPolicy(Allow);
0772     if (proto != GpgME::UnknownProtocol) {
0773         cmd->setProtocol(proto);
0774     }
0775     cmd->start();
0776 }
0777 
0778 void KleopatraApplication::signFiles(const QStringList &files, GpgME::Protocol proto)
0779 {
0780     auto const cmd = new SignEncryptFilesCommand(files, nullptr);
0781     cmd->setSigningPolicy(Force);
0782     cmd->setEncryptionPolicy(Deny);
0783     if (proto != GpgME::UnknownProtocol) {
0784         cmd->setProtocol(proto);
0785     }
0786     cmd->start();
0787 }
0788 
0789 void KleopatraApplication::signEncryptFiles(const QStringList &files, GpgME::Protocol proto)
0790 {
0791     auto const cmd = new SignEncryptFilesCommand(files, nullptr);
0792     if (proto != GpgME::UnknownProtocol) {
0793         cmd->setProtocol(proto);
0794     }
0795     cmd->start();
0796 }
0797 
0798 void KleopatraApplication::decryptFiles(const QStringList &files, GpgME::Protocol /*proto*/)
0799 {
0800     auto const cmd = new DecryptVerifyFilesCommand(files, nullptr);
0801     cmd->setOperation(Decrypt);
0802     cmd->start();
0803 }
0804 
0805 void KleopatraApplication::verifyFiles(const QStringList &files, GpgME::Protocol /*proto*/)
0806 {
0807     auto const cmd = new DecryptVerifyFilesCommand(files, nullptr);
0808     cmd->setOperation(Verify);
0809     cmd->start();
0810 }
0811 
0812 void KleopatraApplication::decryptVerifyFiles(const QStringList &files, GpgME::Protocol /*proto*/)
0813 {
0814     auto const cmd = new DecryptVerifyFilesCommand(files, nullptr);
0815     cmd->start();
0816 }
0817 
0818 void KleopatraApplication::checksumFiles(const QStringList &files, GpgME::Protocol /*proto*/)
0819 {
0820     QStringList verifyFiles, createFiles;
0821 
0822     for (const QString &file : files) {
0823         if (isChecksumFile(file)) {
0824             verifyFiles << file;
0825         } else {
0826             createFiles << file;
0827         }
0828     }
0829 
0830     if (!verifyFiles.isEmpty()) {
0831         auto const cmd = new ChecksumVerifyFilesCommand(verifyFiles, nullptr);
0832         cmd->start();
0833     }
0834     if (!createFiles.isEmpty()) {
0835         auto const cmd = new ChecksumCreateFilesCommand(createFiles, nullptr);
0836         cmd->start();
0837     }
0838 }
0839 
0840 void KleopatraApplication::setIgnoreNewInstance(bool ignore)
0841 {
0842     d->ignoreNewInstance = ignore;
0843 }
0844 
0845 bool KleopatraApplication::ignoreNewInstance() const
0846 {
0847     return d->ignoreNewInstance;
0848 }
0849 
0850 void KleopatraApplication::blockUrl(const QUrl &url)
0851 {
0852     qCDebug(KLEOPATRA_LOG) << "Blocking URL" << url;
0853     KMessageBox::error(mainWindow(), i18n("Opening an external link is administratively prohibited."), i18n("Prohibited"));
0854 }
0855 
0856 void KleopatraApplication::startGpgAgent()
0857 {
0858     Kleo::launchGpgAgent();
0859 }
0860 
0861 void KleopatraApplication::setDistributionSettings(const std::shared_ptr<QSettings> &settings)
0862 {
0863     d->distroSettings = settings;
0864 }
0865 
0866 std::shared_ptr<QSettings> KleopatraApplication::distributionSettings() const
0867 {
0868     return d->distroSettings;
0869 }
0870 
0871 #include "kleopatraapplication.moc"
0872 
0873 #include "moc_kleopatraapplication.cpp"