diff --git a/main/main.cpp b/main/main.cpp index 2ec6614..3787325 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1,77 +1,77 @@ /* SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Sune Stolborg Vuorela SPDX-License-Identifier: GPL-3.0-or-later */ #include "firsttimedialog.h" #include "mainwindow.h" #include #include #include #include #include #include #include #include /** * @brief main * @param argc * @param argv * @return */ #include int main(int argc, char *argv[]) { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif // allow darkmode #if defined(Q_OS_WIN) if (!qEnvironmentVariableIsSet("QT_QPA_PLATFORM")) { qputenv("QT_QPA_PLATFORM", "windows:darkmode=1"); } #endif QApplication app(argc, argv); KLocalizedString::setApplicationDomain("gpgpass"); Q_INIT_RESOURCE(resources); QCoreApplication::setOrganizationName(QStringLiteral("GnuPG")); QCoreApplication::setOrganizationDomain(QStringLiteral("gnupg.org")); QCoreApplication::setApplicationName(QStringLiteral("GnuPGPass")); QCoreApplication::setApplicationVersion(QString::fromUtf8(GPGPASS_VERSION_STRING)); QApplication::setWindowIcon(QIcon(QStringLiteral(":/artwork/64-gpgpass.png"))); QGuiApplication::setDesktopFileName(QStringLiteral("org.gnupg.gpgpass.desktop")); MainWindow w; // ensure KIconThemes is loaded for rcc icons KIconLoader::global()->hasIcon(QString{}); KColorSchemeManager m; - FirstTimeDialog d(&w, w.pass()); + FirstTimeDialog d(&w); QSettings s; if (!s.value(QStringLiteral("setup"), false).toBool()) { if (d.wouldDoSomething()) { d.show(); } else { d.doInitialSettings(); w.show(); } } else { w.show(); } return app.exec(); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7b3637e..fed5b79 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,48 +1,47 @@ # let's put most of the "meat" in a static library this way, we can also unit # test parts of it add_library(gpgpass_internal STATIC) target_sources( gpgpass_internal PRIVATE clipboardhelper.h configdialog.h deselectabletreeview.h filecontent.h firsttimedialog.h gpgmehelpers.h mainwindow.h pass.h passwordconfiguration.h passworddialog.h qpushbuttonfactory.h settings.h storemodel.h userinfo.h usersdialog.h util.h clipboardhelper.cpp configdialog.cpp filecontent.cpp firsttimedialog.cpp mainwindow.cpp pass.cpp passworddialog.cpp settings.cpp storemodel.cpp usersdialog.cpp util.cpp ../resources.qrc ) ki18n_wrap_ui(gpgpass_internal mainwindow.ui configdialog.ui usersdialog.ui - keygenwidget.ui passworddialog.ui userswidget.ui ) target_link_libraries(gpgpass_internal Qt6::Widgets KF6::Prison KF6::IconThemes KF6::I18n KF6::WidgetsAddons QGpgmeQt6) diff --git a/src/firsttimedialog.cpp b/src/firsttimedialog.cpp index 6566748..370ee1e 100644 --- a/src/firsttimedialog.cpp +++ b/src/firsttimedialog.cpp @@ -1,206 +1,155 @@ /* SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Sune Stolborg Vuorela SPDX-License-Identifier: GPL-3.0-or-later */ #include "firsttimedialog.h" #include "qdebug.h" #include -#include #include +#include #include -#include -#include #include "gpgmehelpers.h" #include "settings.h" -#include "ui_keygenwidget.h" #include -#include +#include +#include #include #include -DialogState::DialogState(Pass &p) - : pass(p) -{ -} - QList DialogState::privateKeys() const { auto job = protocol->keyListJob(); std::vector keys; auto result = job->exec(QStringList(), true, keys); if (!isSuccess(result.error())) { return {}; } QList users; for (const auto &key : keys) { UserInfo ui; ui.created = QDateTime::fromSecsSinceEpoch(key.subkey(0).creationTime()); ui.key_id = fromGpgmeCharStar(key.keyID()); ui.name = createCombinedNameString(key.userID(0)); ui.validity = key.userID(0).validityAsString(); ui.expiry = QDateTime::fromSecsSinceEpoch(key.subkey(0).expirationTime()); ui.have_secret = key.hasSecret(); users.append(ui); } return users; } -FirstTimeDialog::FirstTimeDialog(QWidget *mainWindow, Pass &pass) +FirstTimeDialog::FirstTimeDialog(QWidget *mainWindow) : m_mainWindow(mainWindow) - , m_state(pass) { setWindowTitle(i18n("GnuPG Password Manager setup")); setPage(Intro, new IntroPage(m_state)); - setPage(KeyGen, new KeyGenPage(m_state)); - setPage(Done, new DonePage()); QTransform rotate; rotate.rotate(-90); setPixmap(QWizard::WatermarkPixmap, QPixmap(QLatin1String(":/artwork/gnupg-logo-320x100tr.png")).transformed(rotate)); setPixmap(QWizard::LogoPixmap, QPixmap(QLatin1String(":/artwork/64-gpgpass.png"))); setStartId(Intro); } void FirstTimeDialog::doInitialSettings() { QSettings s; s.setValue(QStringLiteral("setup"), true); if (Settings::getAutoclearSeconds() < 5) Settings::setAutoclearSeconds(10); if (Settings::getAutoclearPanelSeconds() < 5) Settings::setAutoclearPanelSeconds(10); Settings::setPassTemplate(QStringLiteral("login\nurl")); } void FirstTimeDialog::done(int i) { if (i == QDialog::DialogCode::Accepted) { doInitialSettings(); m_mainWindow->show(); } QWizard::done(i); } bool FirstTimeDialog::wouldDoSomething() const { return m_state.privateKeys().isEmpty(); } int FirstTimeDialog::nextId() const { switch (currentId()) { - case Intro: - if (m_state.privateKeys().isEmpty()) { - return KeyGen; - } else { - return Done; - } - case KeyGen: - return Done; default: return -1; } - - return -1; }; IntroPage::IntroPage(DialogState &s) : m_state(s) + , m_kleoPath{QStandardPaths::findExecutable(QStringLiteral("kleopatra"))} { + { QVBoxLayout *lay = new QVBoxLayout(); - lay->addWidget(new QLabel(i18n("Welcome to GnuPG Password manager"))); + lay->addWidget(new QLabel(i18n("No private keys found. Please generate a key:"))); + lay->addWidget(new QLabel(i18n("in a terminal, run `gpg --gen-key` with relevant options"))); + if (!m_kleoPath.isEmpty()) { + lay->addWidget(new QLabel(i18n("Or launch kleopatra to help create a key"))); + auto startKleo = new QPushButton(i18n("Launch kleopatra"), this); + connect(startKleo, &QPushButton::clicked, this, [kleoPath = this->m_kleoPath]() { + QProcess::startDetached(kleoPath, QStringList() + << QStringLiteral("--gen-key")); + }); + lay->addWidget(startKleo); + } + lay->addWidget(new QLabel(i18n("Please refresh once done"))); + auto refresh = new QPushButton(i18n("Refresh")); + connect(refresh, &QPushButton::clicked, this, &IntroPage::refresh); + lay->addWidget(refresh); + + m_noKeysContainer = new QWidget; + m_noKeysContainer->setLayout(lay); + } + { + QVBoxLayout *lay = new QVBoxLayout(); + lay->addWidget(new QLabel(i18n("Private keys found. Please close the dialog"))); + m_keysContainer = new QWidget; + m_keysContainer->setLayout(lay); + } + QVBoxLayout *lay = new QVBoxLayout(); + setTitle(i18n("Welcome")); - setSubTitle(i18n("Setting up")); + setSubTitle(i18n("Key generation")); + + lay->addWidget(new QLabel(i18n("Welcome to GnuPG Password manager"))); + lay->addWidget(m_noKeysContainer); + lay->addWidget(m_keysContainer); + m_keysContainer->hide(); + setLayout(lay); + setFinalPage(true); } - -KeyGenPage::KeyGenPage(DialogState &s) - : m_state(s) +bool IntroPage::isComplete() const { - setTitle(i18n("Generate keys")); - setSubTitle(i18n("Generate keys")); - m_ui = std::make_unique(); - m_ui->setupUi(this); - m_ui->spinner->hide(); - m_ui->generateButton->setEnabled(false); - m_ui->message->hide(); - m_ui->email->setValidator( - new QRegularExpressionValidator(QRegularExpression(QRegularExpression::anchoredPattern(QStringLiteral(R"(\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b)")), - QRegularExpression::CaseInsensitiveOption))); - connect(m_ui->generateButton, &QPushButton::clicked, this, &KeyGenPage::startKeyGen); - connect(m_ui->email, &QLineEdit::textChanged, this, &KeyGenPage::checkEntries); - connect(m_ui->name, &QLineEdit::textChanged, this, &KeyGenPage::checkEntries); + return !m_complete; } -void KeyGenPage::startKeyGen() -{ - m_ui->email->setEnabled(false); - m_ui->name->setEnabled(false); - m_ui->generateButton->setEnabled(false); - m_ui->includeComment->setEnabled(false); - m_ui->spinner->show(); - - auto job = m_state.protocol->quickJob(); - connect(job, &QGpgME::QuickJob::result, this, [this](auto &&result, const QString &log, auto &&logResult) { - if (isSuccess(result)) { - Q_EMIT this->completeChanged(); - this->m_ui->spinner->hide(); - this->m_ui->message->show(); - this->m_ui->message->setText(i18n("Key generated")); - } else { - this->m_ui->message->setText(fromGpgmeCharStar(result.asString())); - this->m_ui->message->show(); - } - Q_UNUSED(log); - - Q_UNUSED(logResult); - }); - QString uid; - if (m_ui->includeComment->isChecked()) { - uid = QStringLiteral("%1 (PasswordManager) <%2>"); +void IntroPage::refresh() { + bool pageComplete = m_state.privateKeys().isEmpty(); + if (pageComplete) { + m_noKeysContainer->hide(); + m_keysContainer->show(); } else { - uid = QStringLiteral("%1 <%2>"); + m_noKeysContainer->hide(); + m_keysContainer->show(); } - job->startCreate(uid.arg(m_ui->name->text(), m_ui->email->text()), "future-default"); -} - -void KeyGenPage::checkEntries() -{ - auto emailValidator = m_ui->email->validator(); - bool enable = false; - if (emailValidator) { - auto email = m_ui->email->text(); - int i = 0; - if (emailValidator->validate(email, i) == QValidator::Acceptable) { - qDebug() << "email valid"; - enable = m_ui->name->text().size() > 4; - - } else { - qDebug() << "email invalid"; - } - } else { - qDebug() << "no email validator"; - // TODO_REMOV - enable = true; + if (pageComplete) { + m_complete = pageComplete; + Q_EMIT completeChanged(); } - m_ui->generateButton->setEnabled(enable); -} - -KeyGenPage::~KeyGenPage() = default; -bool KeyGenPage::isComplete() const -{ - return !m_state.privateKeys().isEmpty(); -} - -DonePage::DonePage() -{ - setTitle(i18n("Setup done")); - setSubTitle(i18n("Thanks")); - setFinalPage(true); } diff --git a/src/firsttimedialog.h b/src/firsttimedialog.h index cd88ec0..3d41d0a 100644 --- a/src/firsttimedialog.h +++ b/src/firsttimedialog.h @@ -1,76 +1,56 @@ /* SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Sune Stolborg Vuorela SPDX-License-Identifier: GPL-3.0-or-later */ #ifndef FIRSTTIMEDIALOG_H #define FIRSTTIMEDIALOG_H #include "usersdialog.h" #include #include #include -class Ui_KeyGenWidget; - class DialogState : public QObject { Q_OBJECT public: - explicit DialogState(Pass &p); QList privateKeys() const; QGpgME::Protocol *protocol = QGpgME::openpgp(); - QString storePath; - Pass &pass; }; class FirstTimeDialog : public QWizard { enum Pages { Intro, KeyGen, Done }; Q_OBJECT public: - FirstTimeDialog(QWidget *mainWindow, Pass &pass); + FirstTimeDialog(QWidget *mainWindow); int nextId() const override; void done(int i) override; bool wouldDoSomething() const; void doInitialSettings(); private: QWidget *m_mainWindow; DialogState m_state; }; class IntroPage : public QWizardPage { Q_OBJECT public: explicit IntroPage(DialogState &s); - -private: - DialogState &m_state; -}; - -class KeyGenPage : public QWizardPage -{ - Q_OBJECT -public: - explicit KeyGenPage(DialogState &s); bool isComplete() const override; - ~KeyGenPage(); -private Q_SLOTS: - void startKeyGen(); - void checkEntries(); +private Q_SLOTS: + void refresh(); private: DialogState &m_state; - std::unique_ptr m_ui; -}; - -class DonePage : public QWizardPage -{ -public: - DonePage(); + QString m_kleoPath; + bool m_complete = false; + QWidget* m_noKeysContainer; + QWidget* m_keysContainer; }; #endif // FIRSTTIMEDIALOG_H diff --git a/src/keygenwidget.ui b/src/keygenwidget.ui deleted file mode 100644 index cfc5f5f..0000000 --- a/src/keygenwidget.ui +++ /dev/null @@ -1,207 +0,0 @@ - - - KeyGenWidget - - - - 0 - 0 - 606 - 242 - - - - - - - - 0 - 0 - - - - Generate a new key pair - - - true - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - - - - 0 - 0 - - - - Qt::LeftToRight - - - Email - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - email - - - - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - Qt::LeftToRight - - - Name - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - name - - - - - - - - 0 - 0 - - - - - - - - Puts PasswordManager in the comment field of the key. Useful if this key is dedicated for Password Manager usage and not to be confused with other keys - - - Include PasswordManager in the uid - - - true - - - - - - - - - - 0 - - - -1 - - - - - - - - - - TextLabel - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 505 - 20 - - - - - - - - Generate - - - - - - - - - - email - name - - - - - accept() - reject() - - diff --git a/src/mainwindow.h b/src/mainwindow.h index bc87603..fd88bde 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -1,136 +1,131 @@ /* SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer SPDX-FileCopyrightText: 2016-2017 tezeb SPDX-FileCopyrightText: 2018 Lukas Vogel SPDX-FileCopyrightText: 2018 Claudio Maradonna SPDX-FileCopyrightText: 2019Maciej S. Szmigiero SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Sune Stolborg Vuorela SPDX-License-Identifier: GPL-3.0-or-later */ #ifndef MAINWINDOW_H_ #define MAINWINDOW_H_ #include "storemodel.h" #include #include #include #include #include #ifdef __APPLE__ // http://doc.qt.io/qt-5/qkeysequence.html#qt_set_sequence_auto_mnemonic void qt_set_sequence_auto_mnemonic(bool b); #endif namespace Ui { class MainWindow; } /*! \class MainWindow \brief The MainWindow class does way too much, not only is it a switchboard, configuration handler and more, it's also the process-manager. This class could really do with an overhaul. */ class QComboBox; class ClipboardHelper; class Pass; class KMessageWidget; class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); void restoreWindow(); void userDialog(QString = {}); void config(); void setUiElementsEnabled(bool state); const QModelIndex getCurrentTreeViewIndex(); void setVisible(bool visible) override; - Pass &pass() - { - return *m_pass; - } - protected: void closeEvent(QCloseEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void changeEvent(QEvent *event) override; bool eventFilter(QObject *obj, QEvent *event) override; public Q_SLOTS: void deselect(); void critical(const QString&, const QString&); void passShowHandler(const QString &); void selectTreeItem(const QModelIndex &index); void startReencryptPath(); void endReencryptPath(); private Q_SLOTS: void addPassword(); void addFolder(); void onEdit(); void onDelete(); void onUsers(); void onConfig(); void editTreeItem(const QModelIndex &index); void clearPanel(); void filterList(const QString &arg1); void selectFromSearch(); void selectProfile(QString); void showContextMenu(const QPoint &pos); void renameFolder(); void editPassword(const QString &); void renamePassword(); void focusInput(); void onTimeoutSearch(); void verifyInitialized(); private: std::unique_ptr m_pass; ClipboardHelper *m_clipboardHelper; QScopedPointer ui; QFileSystemModel model; StoreModel proxyModel; QScopedPointer selectionModel; QTimer clearPanelTimer, searchTimer; KMessageWidget* m_notInitialized; KMessageWidget* m_errorMessage; QAction* m_profiles; QComboBox* m_profileBox; bool firstShow = true; void initToolBarButtons(); void initStatusBar(); void showRemainingHtml(const QString &text); void updateText(); void selectFirstFile(); QModelIndex firstFile(QModelIndex parentIndex); void setPassword(QString, bool isNew = true); void updateProfileBox(); void initTrayIcon(); void destroyTrayIcon(); void clearTemplateWidgets(); void reencryptPath(QString dir); void addToGridLayout(const QString &field, const QString &value); }; #endif // MAINWINDOW_H_