Page MenuHome GnuPG

No OneTemporary

diff --git a/main/main.cpp b/main/main.cpp
index 967d8ea..2743402 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1,72 +1,72 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "firsttimedialog.h"
#include "mainwindow.h"
#include <QApplication>
#include <QDir>
#include <QTranslator>
#include <QtWidgets>
#include <gpgpass_version.h>
#include <KIconLoader>
#include <KColorSchemeManager>
#include <KLocalizedString>
/**
* @brief main
* @param argc
* @param argv
* @return
*/
#include <sys/stat.h>
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());
QSettings s;
- if (!s.value(QStringLiteral("setup"), false).toBool()) {
+ if (!s.value(QStringLiteral("setup"), false).toBool() && d.wouldDoSomething()) {
d.show();
} else {
w.show();
}
return app.exec();
}
diff --git a/src/firsttimedialog.cpp b/src/firsttimedialog.cpp
index 396769a..9850730 100644
--- a/src/firsttimedialog.cpp
+++ b/src/firsttimedialog.cpp
@@ -1,296 +1,205 @@
/*
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "firsttimedialog.h"
#include "qdebug.h"
#include <QPixmap>
#include <QFileDialog>
#include <QHBoxLayout>
#include <QLabel>
#include <QProgressBar>
#include <QRegularExpressionValidator>
#include "gpgmehelpers.h"
#include "settings.h"
#include "ui_keygenwidget.h"
#include "ui_selectpasswordstore.h"
#include "ui_userswidget.h"
#include "util.h"
#include <QGpgME/KeyListJob>
#include <QGpgME/QuickJob>
#include <gpgme++/keylistresult.h>
#include <KLocalizedString>
DialogState::DialogState(Pass &p)
: pass(p)
{
}
QList<UserInfo> DialogState::privateKeys() const
{
auto job = protocol->keyListJob();
std::vector<GpgME::Key> keys;
auto result = job->exec(QStringList(), true, keys);
if (!isSuccess(result.error())) {
return {};
}
QList<UserInfo> 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)
: 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(PasswordStore, new PasswordStorePage(m_state));
- setPage(KeySelect, new KeySelectPage(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::done(int i)
{
if (i == QDialog::DialogCode::Accepted) {
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"));
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 PasswordStore;
- }
- case KeyGen:
- return PasswordStore;
- case PasswordStore:
- if (QFile::exists(m_state.storePath + QStringLiteral("/.gpg-id"))) {
return Done;
- } else {
- return KeySelect;
}
- case KeySelect:
+ case KeyGen:
return Done;
default:
return -1;
}
return -1;
};
IntroPage::IntroPage(DialogState &s)
: m_state(s)
{
QVBoxLayout *lay = new QVBoxLayout();
lay->addWidget(new QLabel(i18n("Welcome to GnuPG Password manager")));
setTitle(i18n("Welcome"));
setSubTitle(i18n("Setting up"));
setLayout(lay);
}
KeyGenPage::KeyGenPage(DialogState &s)
: m_state(s)
{
setTitle(i18n("Generate keys"));
setSubTitle(i18n("Generate keys"));
m_ui = std::make_unique<Ui_KeyGenWidget>();
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);
}
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>");
} else {
uid = QStringLiteral("%1 <%2>");
}
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;
}
m_ui->generateButton->setEnabled(enable);
}
KeyGenPage::~KeyGenPage() = default;
bool KeyGenPage::isComplete() const
{
return !m_state.privateKeys().isEmpty();
}
-PasswordStorePage::PasswordStorePage(DialogState &s)
- : m_ui(std::make_unique<Ui_SelectPasswordStore>())
- , m_state(s)
-{
- setTitle(i18n("Select password store"));
- setSubTitle(i18n("Folder for selection"));
- m_ui->setupUi(this);
- m_ui->lineEdit->setText(Util::findPasswordStore());
- connect(m_ui->pushButton, &QPushButton::clicked, this, [this]() {
- QFileDialog dialog(this);
- dialog.setFileMode(QFileDialog::Directory);
- dialog.setFilter(QDir::NoFilter);
- dialog.setOption(QFileDialog::ShowDirsOnly);
- QString result;
- if (dialog.exec())
- result = dialog.selectedFiles().constFirst();
-
- m_ui->lineEdit->setText(result);
- });
- m_ui->label->hide();
- connect(m_ui->lineEdit, &QLineEdit::textChanged, this, &PasswordStorePage::completeChanged);
- connect(m_ui->checkBox, &QCheckBox::stateChanged, this, &PasswordStorePage::completeChanged);
-}
-
-bool PasswordStorePage::isComplete() const
-{
- QString dir = m_ui->lineEdit->text();
- if (QDir(dir).exists()) {
- m_ui->label->hide();
- return true;
- }
- if (m_ui->checkBox->isChecked()) {
- m_ui->label->hide();
- return true;
- }
- m_ui->label->show();
- return false;
-}
-
-bool PasswordStorePage::validatePage()
-{
- m_state.storePath = Util::normalizeFolderPath(QDir::fromNativeSeparators(m_ui->lineEdit->text()));
- Settings::setPassStore(m_state.storePath);
- return QDir().mkpath(m_state.storePath);
-}
-
-PasswordStorePage::~PasswordStorePage() = default;
-
-KeySelectPage::KeySelectPage(DialogState &s)
- : m_ui(std::make_unique<Ui_UsersWidget>())
- , m_state(s)
- , d(m_state.pass)
-{
- setTitle(i18n("KeySelectPage"));
- setSubTitle(i18n("Describe users with access"));
- m_ui->setupUi(this);
- d.setUi(m_ui.get());
- connect(m_ui->listWidget, &QListWidget::itemChanged, this, &KeySelectPage::completeChanged);
-}
-
-void KeySelectPage::initializePage()
-{
- d.m_dir = m_state.storePath;
- d.generateUserList();
- d.populateList();
-}
-
-bool KeySelectPage::isComplete() const
-{
- for (const auto &item : std::as_const(d.m_userList)) {
- if (item.enabled) {
- return true;
- }
- }
- return false;
-}
-
-bool KeySelectPage::validatePage()
-{
- d.init();
- return true;
-}
-
-KeySelectPage::~KeySelectPage() = default;
-
DonePage::DonePage()
{
setTitle(i18n("Setup done"));
setSubTitle(i18n("Thanks"));
setFinalPage(true);
}
diff --git a/src/firsttimedialog.h b/src/firsttimedialog.h
index 759046a..3aa6c1c 100644
--- a/src/firsttimedialog.h
+++ b/src/firsttimedialog.h
@@ -1,107 +1,78 @@
/*
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef FIRSTTIMEDIALOG_H
#define FIRSTTIMEDIALOG_H
#include "usersdialog.h"
#include <QGpgME/Protocol>
#include <QWizard>
#include <userinfo.h>
class Ui_KeyGenWidget;
class Ui_UsersWidget;
class Ui_KeyGenWidget;
class Ui_SelectPasswordStore;
class DialogState : public QObject
{
Q_OBJECT
public:
explicit DialogState(Pass &p);
QList<UserInfo> privateKeys() const;
QGpgME::Protocol *protocol = QGpgME::openpgp();
QString storePath;
Pass &pass;
};
class FirstTimeDialog : public QWizard
{
- enum Pages { Intro, KeyGen, PasswordStore, KeySelect, Done };
+ enum Pages { Intro, KeyGen, Done };
Q_OBJECT
public:
FirstTimeDialog(QWidget *mainWindow, Pass &pass);
int nextId() const override;
void done(int i) override;
+ bool wouldDoSomething() const;
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:
DialogState &m_state;
std::unique_ptr<Ui_KeyGenWidget> m_ui;
};
-class PasswordStorePage : public QWizardPage
-{
- Q_OBJECT
-public:
- explicit PasswordStorePage(DialogState &s);
- bool validatePage() override;
- ~PasswordStorePage();
- bool isComplete() const override;
-
-private:
- std::unique_ptr<Ui_SelectPasswordStore> m_ui;
- DialogState &m_state;
-};
-
-class KeySelectPage : public QWizardPage
-{
- Q_OBJECT
-public:
- explicit KeySelectPage(DialogState &s);
- bool isComplete() const override;
- bool validatePage() override;
- void initializePage() override;
- ~KeySelectPage();
-
-private:
- std::unique_ptr<Ui_UsersWidget> m_ui;
- DialogState &m_state;
- UsersWidgetData d;
-};
-
class DonePage : public QWizardPage
{
public:
DonePage();
};
#endif // FIRSTTIMEDIALOG_H
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 48a4186..a15c181 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1,988 +1,994 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2016-2017 tezeb <tezeb+github@outoftheblue.pl>
SPDX-FileCopyrightText: 2018 Lukas Vogel <lukedirtwalker@gmail.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2019Maciej S. Szmigiero <mail@maciej.szmigiero.name>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "mainwindow.h"
#include <gpgpass_version.h>
#include "clipboardhelper.h"
#include "configdialog.h"
#include "filecontent.h"
#include "pass.h"
#include "passworddialog.h"
#include "qpushbuttonfactory.h"
#include "settings.h"
#include "ui_mainwindow.h"
#include "usersdialog.h"
#include "util.h"
#include <Prison/Barcode>
#include <KMessageWidget>
#include <KLocalizedString>
#include <QCloseEvent>
#include <QFileInfo>
#include <QInputDialog>
#include <QLabel>
#include <QMenu>
#include <QMessageBox>
#include <QShortcut>
#include <QTimer>
#include <QComboBox>
#include <QDebug>
static QString directoryName(const QString &dirOrFile)
{
QFileInfo fi{dirOrFile};
if (fi.isDir()) {
return fi.absoluteFilePath();
} else {
return fi.absolutePath();
}
}
/**
* @brief MainWindow::MainWindow handles all of the main functionality and also
* the main window.
* @param searchText for searching from cli
* @param parent pointer
*/
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, m_pass{std::make_unique<Pass>()}
, ui(new Ui::MainWindow)
, proxyModel{*m_pass}
{
#ifdef __APPLE__
// extra treatment for mac os
// see http://doc.qt.io/qt-5/qkeysequence.html#qt_set_sequence_auto_mnemonic
qt_set_sequence_auto_mnemonic(true);
#endif
ui->setupUi(this);
m_clipboardHelper = new ClipboardHelper(this);
m_errorMessage = new KMessageWidget();
m_errorMessage->setMessageType(KMessageWidget::Error);
m_errorMessage->hide();
ui->messagesArea->addWidget(m_errorMessage);
connect(m_pass.get(), &Pass::errorString, this, [this](auto str) {
m_errorMessage->setText(str);
m_errorMessage->animatedShow();
setUiElementsEnabled(true);
});
connect(m_pass.get(), &Pass::critical, this, &MainWindow::critical);
connect(m_pass.get(), &Pass::finishedShow, this, &MainWindow::passShowHandler);
connect(m_pass.get(), &Pass::finishedInsert, this, [this]() {
this->selectTreeItem(this->getCurrentTreeViewIndex());
});
connect(m_pass.get(), &Pass::startReencryptPath, this, &MainWindow::startReencryptPath);
connect(m_pass.get(), &Pass::endReencryptPath, this, &MainWindow::endReencryptPath);
{
m_notInitialized = new KMessageWidget(i18n("Password store not initialized"));
m_notInitialized->setCloseButtonVisible(false);
QAction* action = new QAction(i18n("Initialize with users"));
connect(action, &QAction::triggered, this, [this](){this->userDialog();});
m_notInitialized->addAction(action);
m_notInitialized->setMessageType(KMessageWidget::Error);
ui->messagesArea->addWidget(m_notInitialized);
m_notInitialized->hide();
}
{
auto w = new QWidget();
w->setLayout(new QHBoxLayout);
auto l = new QLabel(i18n("Select profile"));
auto sep = ui->toolBar->addSeparator();
w->layout()->addWidget(l);
m_profileBox = new QComboBox();
w->layout()->addWidget(m_profileBox);
m_profiles = ui->toolBar->addWidget(w);
connect(m_profiles, &QAction::changed, sep, [this, sep]() { sep->setVisible(this->m_profiles->isVisible()); });
}
}
void MainWindow::setVisible(bool visible)
{
// This originated in the ctor, but we want this to happen after the first start wizard has been run
// so for now, moved to show() on first call
if (visible && firstShow) {
firstShow = false;
// register shortcut ctrl/cmd + Q to close the main window
new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q), this, SLOT(close()));
model.setNameFilters(QStringList() << QStringLiteral("*.gpg"));
model.setNameFilterDisables(false);
/*
* I added this to solve Windows bug but now on GNU/Linux the main folder,
* if hidden, disappear
*
* model.setFilter(QDir::NoDot);
*/
- QString passStore = Settings::getPassStore(Util::findPasswordStore());
+ auto defaultPassStore = Util::findPasswordStore();
+
+ QString passStore = Settings::getPassStore(defaultPassStore);
+ if (passStore == defaultPassStore) {
+ // let's write it back
+ Settings::setPassStore(passStore);
+ }
QModelIndex rootDir = model.setRootPath(passStore);
model.fetchMore(rootDir);
proxyModel.setSourceModel(&model);
selectionModel.reset(new QItemSelectionModel(&proxyModel));
ui->treeView->setModel(&proxyModel);
ui->treeView->setRootIndex(proxyModel.mapFromSource(rootDir));
ui->treeView->setColumnHidden(1, true);
ui->treeView->setColumnHidden(2, true);
ui->treeView->setColumnHidden(3, true);
ui->treeView->setHeaderHidden(true);
ui->treeView->setIndentation(15);
ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
ui->treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
ui->treeView->sortByColumn(0, Qt::AscendingOrder);
connect(ui->treeView, &QWidget::customContextMenuRequested, this, &MainWindow::showContextMenu);
connect(ui->treeView, &DeselectableTreeView::emptyClicked, this, &MainWindow::deselect);
if (Settings::isNoLineWrapping()) {
ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
}
ui->textBrowser->setOpenExternalLinks(true);
ui->textBrowser->setContextMenuPolicy(Qt::DefaultContextMenu);
updateProfileBox();
clearPanelTimer.setInterval(1000 * Settings::getAutoclearPanelSeconds());
clearPanelTimer.setSingleShot(true);
connect(&clearPanelTimer, SIGNAL(timeout()), this, SLOT(clearPanel()));
searchTimer.setInterval(350);
searchTimer.setSingleShot(true);
connect(&searchTimer, &QTimer::timeout, this, &MainWindow::onTimeoutSearch);
initToolBarButtons();
initStatusBar();
ui->lineEdit->setClearButtonEnabled(true);
setUiElementsEnabled(true);
QTimer::singleShot(10, this, SLOT(focusInput()));
verifyInitialized();
}
QMainWindow::setVisible(visible);
}
MainWindow::~MainWindow() = default;
/**
* @brief MainWindow::focusInput selects any text (if applicable) in the search
* box and sets focus to it. Allows for easy searching, called at application
* start and when receiving empty message in MainWindow::messageAvailable when
* compiled with SINGLE_APP=1 (default).
*/
void MainWindow::focusInput()
{
ui->lineEdit->selectAll();
ui->lineEdit->setFocus();
}
/**
* @brief MainWindow::changeEvent sets focus to the search box
* @param event
*/
void MainWindow::changeEvent(QEvent *event)
{
QWidget::changeEvent(event);
if (event->type() == QEvent::ActivationChange) {
if (isActiveWindow()) {
focusInput();
}
}
}
/**
* @brief MainWindow::initToolBarButtons init main ToolBar and connect actions
*/
void MainWindow::initToolBarButtons()
{
connect(ui->actionAddPassword, &QAction::triggered, this, &MainWindow::addPassword);
connect(ui->actionAddFolder, &QAction::triggered, this, &MainWindow::addFolder);
connect(ui->actionEdit, &QAction::triggered, this, &MainWindow::onEdit);
connect(ui->actionDelete, &QAction::triggered, this, &MainWindow::onDelete);
connect(ui->actionUsers, &QAction::triggered, this, &MainWindow::onUsers);
connect(ui->actionConfig, &QAction::triggered, this, &MainWindow::onConfig);
connect(ui->treeView, &QTreeView::clicked, this, &MainWindow::selectTreeItem);
connect(ui->treeView, &QTreeView::doubleClicked, this, &MainWindow::editTreeItem);
connect(m_profileBox, &QComboBox::currentTextChanged, this, &MainWindow::selectProfile);
connect(ui->lineEdit, &QLineEdit::textChanged, this, &MainWindow::filterList);
connect(ui->lineEdit, &QLineEdit::returnPressed, this, &MainWindow::selectFromSearch);
ui->actionAddPassword->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
ui->actionAddFolder->setIcon(QIcon::fromTheme(QStringLiteral("folder-new")));
ui->actionEdit->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
ui->actionDelete->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
ui->actionUsers->setIcon(QIcon::fromTheme(QStringLiteral("x-office-address-book")));
ui->actionConfig->setIcon(QIcon::fromTheme(QStringLiteral("applications-system")));
}
/**
* @brief MainWindow::initStatusBar init statusBar with default message and logo
*/
void MainWindow::initStatusBar()
{
ui->statusBar->showMessage(i18nc("placeholder is version number","Welcome to GnuPG Password Manager %1", QString::fromLocal8Bit(GPGPASS_VERSION_STRING)));
QPixmap logo = QPixmap(QStringLiteral(":/artwork/32-gpgpass.png"));
QLabel *logoApp = new QLabel(statusBar());
logoApp->setPixmap(logo);
statusBar()->addPermanentWidget(logoApp);
}
const QModelIndex MainWindow::getCurrentTreeViewIndex()
{
return ui->treeView->currentIndex();
}
void MainWindow::showRemainingHtml(const QString &text)
{
QString _text = text;
if (!ui->textBrowser->toPlainText().isEmpty())
_text = ui->textBrowser->toHtml() + _text;
ui->textBrowser->setHtml(_text);
}
/**
* @brief MainWindow::config pops up the configuration screen and handles all
* inter-window communication
*/
void MainWindow::config()
{
QScopedPointer<ConfigDialog> d(new ConfigDialog(this));
d->setModal(true);
if (d->exec()) {
if (d->result() == QDialog::Accepted) {
// Update the textBrowser line wrap mode
if (Settings::isNoLineWrapping()) {
ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
} else {
ui->textBrowser->setLineWrapMode(QTextBrowser::WidgetWidth);
}
this->show();
updateProfileBox();
ui->treeView->setRootIndex(proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore())));
clearPanelTimer.setInterval(1000 * Settings::getAutoclearPanelSeconds());
m_clipboardHelper->setClipboardTimer();
}
}
}
/**
* @brief MainWindow::on_treeView_clicked read the selected password file
* @param index
*/
void MainWindow::selectTreeItem(const QModelIndex &index)
{
bool cleared = ui->treeView->currentIndex().flags() == Qt::NoItemFlags;
// TODO(bezet): "Could not decrypt";
m_clipboardHelper->clearClippedText();
QString file = index.data(QFileSystemModel::FilePathRole).toString();
ui->passwordName->setText(index.data().toString());
if (!file.isEmpty() && QFileInfo(file).isFile() && !cleared) {
m_pass->Show(file);
} else {
clearPanel();
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(true);
}
}
/**
* @brief MainWindow::on_treeView_doubleClicked when doubleclicked on
* TreeViewItem, open the edit Window
* @param index
*/
void MainWindow::editTreeItem(const QModelIndex &index)
{
QFileInfo fileOrFolder{index.data(QFileSystemModel::Roles::FilePathRole).toString()};
if (fileOrFolder.isFile()) {
editPassword(fileOrFolder.absoluteFilePath());
}
}
/**
* @brief MainWindow::deselect clear the selection, password and copy buffer
*/
void MainWindow::deselect()
{
m_clipboardHelper->clearClipboard();
ui->treeView->clearSelection();
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(false);
ui->passwordName->setText(QString{});
clearPanel();
}
void MainWindow::passShowHandler(const QString &p_output)
{
QStringList templ = Settings::isUseTemplate() ? Settings::getPassTemplate().split(QStringLiteral("\n")) : QStringList();
bool allFields = Settings::isUseTemplate() && Settings::isTemplateAllFields();
FileContent fileContent = FileContent::parse(p_output, templ, allFields);
QString output = p_output;
QString password = fileContent.getPassword();
// set clipped text
m_clipboardHelper->setClippedText(password);
// first clear the current view:
clearPanel();
// show what is needed:
if (Settings::isHideContent()) {
output = i18n("*** Content hidden ***");
} else if (!Settings::isDisplayAsIs()) {
if (!password.isEmpty()) {
// set the password, it is hidden if needed in addToGridLayout
addToGridLayout(i18n("Password"), password);
}
const NamedValues namedValues = fileContent.getNamedValues();
for (const auto& nv : namedValues) {
addToGridLayout(nv.name, nv.value);
}
output = fileContent.getRemainingDataForDisplay();
}
if (Settings::isUseAutoclearPanel()) {
clearPanelTimer.start();
}
output = output.toHtmlEscaped();
output.replace(Util::protocolRegex(), QStringLiteral(R"(<a href="\1">\1</a>)"));
output.replace(QStringLiteral("\n"), QStringLiteral("<br />"));
showRemainingHtml(output);
setUiElementsEnabled(true);
m_errorMessage->animatedHide();
}
/**
* @brief MainWindow::clearPanel hide the information from shoulder surfers
*/
void MainWindow::clearPanel()
{
clearTemplateWidgets();
ui->textBrowser->setHtml(QString{});
}
/**
* @brief MainWindow::setUiElementsEnabled enable or disable the relevant UI
* elements
* @param state
*/
void MainWindow::setUiElementsEnabled(bool state)
{
ui->treeView->setEnabled(state);
ui->lineEdit->setEnabled(state);
ui->lineEdit->installEventFilter(this);
ui->actionAddPassword->setEnabled(state);
ui->actionAddFolder->setEnabled(state);
ui->actionUsers->setEnabled(state);
ui->actionConfig->setEnabled(state);
// is a file selected?
state &= ui->treeView->currentIndex().isValid();
ui->actionDelete->setEnabled(state);
ui->actionEdit->setEnabled(state);
}
/**
* @brief MainWindow::on_configButton_clicked run Mainwindow::config
*/
void MainWindow::onConfig()
{
config();
}
/**
* @brief Executes when the string in the search box changes, collapses the
* TreeView
* @param arg1
*/
void MainWindow::filterList(const QString &arg1)
{
ui->statusBar->showMessage(i18n("Looking for: %1", arg1), 1000);
ui->treeView->expandAll();
clearPanel();
ui->passwordName->setText(QString{});
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(false);
searchTimer.start();
}
/**
* @brief MainWindow::onTimeoutSearch Fired when search is finished or too much
* time from two keypresses is elapsed
*/
void MainWindow::onTimeoutSearch()
{
QString query = ui->lineEdit->text();
if (query.isEmpty()) {
ui->treeView->collapseAll();
deselect();
}
query.replace(QStringLiteral(" "), QStringLiteral(".*"));
QRegularExpression regExp(query, QRegularExpression::CaseInsensitiveOption);
proxyModel.setFilterRegularExpression(regExp);
ui->treeView->setRootIndex(proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore())));
if (proxyModel.rowCount() > 0 && !query.isEmpty()) {
selectFirstFile();
} else {
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(false);
}
}
/**
* @brief MainWindow::on_lineEdit_returnPressed get searching
*
* Select the first possible file in the tree
*/
void MainWindow::selectFromSearch()
{
if (proxyModel.rowCount() > 0) {
selectFirstFile();
selectTreeItem(ui->treeView->currentIndex());
}
}
/**
* @brief MainWindow::selectFirstFile select the first possible file in the
* tree
*/
void MainWindow::selectFirstFile()
{
QModelIndex index = proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore()));
index = firstFile(index);
ui->treeView->setCurrentIndex(index);
}
/**
* @brief MainWindow::firstFile return location of first possible file
* @param parentIndex
* @return QModelIndex
*/
QModelIndex MainWindow::firstFile(QModelIndex parentIndex)
{
QModelIndex index = parentIndex;
int numRows = proxyModel.rowCount(parentIndex);
for (int row = 0; row < numRows; ++row) {
index = proxyModel.index(row, 0, parentIndex);
if (model.fileInfo(proxyModel.mapToSource(index)).isFile())
return index;
if (proxyModel.hasChildren(index))
return firstFile(index);
}
return index;
}
/**
* @brief MainWindow::setPassword open passworddialog
* @param file which pgp file
* @param isNew insert (not update)
*/
void MainWindow::setPassword(QString file, bool isNew)
{
PasswordDialog d(*m_pass, file, isNew, this);
if (!d.exec()) {
ui->treeView->setFocus();
}
}
/**
* @brief MainWindow::addPassword add a new password by showing a
* number of dialogs.
*/
void MainWindow::addPassword()
{
bool ok;
QString dir = directoryName(ui->treeView->currentIndex().data(QFileSystemModel::Roles::FilePathRole).toString());
if (dir.isEmpty()) {
dir = Settings::getPassStore();
}
QString file = QInputDialog::getText(this, i18n("New file"), i18n("New password file: \n(Will be placed in %1 )", dir), QLineEdit::Normal, QString{}, &ok);
if (!ok || file.isEmpty())
return;
file = QDir(dir).absoluteFilePath(file + QStringLiteral(".gpg"));
setPassword(file);
}
/**
* @brief MainWindow::onDelete remove password, if you are
* sure.
*/
void MainWindow::onDelete()
{
QModelIndex currentIndex = ui->treeView->currentIndex();
if (!currentIndex.isValid()) {
// If not valid, we might end up passing empty string
// to delete, and taht might delete unexpected things on disk
return;
}
QFileInfo fileOrFolder{currentIndex.data(QFileSystemModel::FilePathRole).toString()};
bool isDir = fileOrFolder.isDir();
QString file = fileOrFolder.absoluteFilePath();
QString message;
if (isDir) {
message = i18nc("deleting a folder; placeholder is folder name","Are you sure you want to delete %1 and the whole content?", file);
QDirIterator it(model.rootPath() + QLatin1Char('/') + file, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
if (auto fi = it.fileInfo(); fi.isFile()) {
if (fi.suffix() != QStringLiteral("gpg")) {
message += QStringLiteral("<br><strong>") + i18nc("extra warning during certain folder deletions","Attention: "
"there are unexpected files in the given folder, "
"check them before continue") + QStringLiteral("</strong>");
break;
}
}
}
} else {
message = i18nc("deleting a file; placeholder is file name","Are you sure you want to delete %1?", file);
}
if (QMessageBox::question(this,
isDir ? i18n("Delete folder?") : i18n("Delete password?"),
message,
QMessageBox::Yes | QMessageBox::No)
!= QMessageBox::Yes)
return;
m_pass->Remove(file, isDir);
}
/**
* @brief MainWindow::onEdit try and edit (selected) password.
*/
void MainWindow::onEdit()
{
QString file = ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString();
if (!file.isEmpty()) {
editPassword(file);
}
}
/**
* @brief MainWindow::userDialog see MainWindow::onUsers()
* @param dir folder to edit users for.
*/
void MainWindow::userDialog(QString dir)
{
if (dir.isEmpty()) {
dir = Settings::getPassStore();
}
QFileInfo fi(dir);
if (!fi.isDir()) {
dir = fi.absolutePath();
}
UsersDialog d(dir, *m_pass, this);
if (!d.exec()) {
ui->treeView->setFocus();
}
verifyInitialized();
}
/**
* @brief MainWindow::onUsers edit users for the current
* folder,
* gets lists and opens UserDialog.
*/
void MainWindow::onUsers()
{
QString dir = ui->treeView->currentIndex().data(QFileSystemModel::Roles::FilePathRole).toString();
if (dir.isEmpty()) {
dir = Settings::getPassStore();
} else {
QFileInfo fi(dir);
if (!fi.isDir()) {
dir = fi.absolutePath();
}
dir = Util::normalizeFolderPath(dir);
}
userDialog(dir);
}
/**
* @brief MainWindow::updateProfileBox update the list of profiles, optionally
* select a more appropriate one to view too
*/
void MainWindow::updateProfileBox()
{
QHash<QString, QString> profiles = Settings::getProfiles();
if (profiles.isEmpty()) {
m_profiles->setVisible(false);
} else {
m_profiles->setVisible(true);
m_profileBox->setEnabled(profiles.size() > 1);
m_profileBox->clear();
QHashIterator<QString, QString> i(profiles);
while (i.hasNext()) {
i.next();
if (!i.key().isEmpty())
m_profileBox->addItem(i.key());
}
m_profileBox->model()->sort(0);
}
int index = m_profileBox->findText(Settings::getProfile());
if (index != -1) // -1 for not found
m_profileBox->setCurrentIndex(index);
}
/**
* @brief MainWindow::on_profileBox_currentIndexChanged make sure we show the
* correct "profile"
* @param name
*/
void MainWindow::selectProfile(QString name)
{
if (name == Settings::getProfile())
return;
ui->lineEdit->clear();
clearPanel();
Settings::setProfile(name);
Settings::setPassStore(Settings::getProfiles().value(name));
ui->statusBar->showMessage(i18n("Profile changed to %1", name), 2000);
ui->treeView->selectionModel()->clear();
ui->treeView->setRootIndex(proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore())));
verifyInitialized();
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(false);
}
void MainWindow::verifyInitialized() {
bool actionsEnabled;
if (!QFile::exists(Settings::getPassStore() + QStringLiteral("/.gpg-id"))) {
m_notInitialized->animatedShow();
actionsEnabled = false;
} else {
m_notInitialized->animatedHide();
actionsEnabled = true;
}
ui->actionAddFolder->setEnabled(actionsEnabled);
ui->actionAddPassword->setEnabled(actionsEnabled);
ui->actionDelete->setEnabled(ui->actionDelete->isEnabled() && actionsEnabled);
ui->actionEdit->setEnabled(ui->actionEdit->isEnabled() && actionsEnabled);
}
/**
* @brief MainWindow::closeEvent hide or quit
* @param event
*/
void MainWindow::closeEvent(QCloseEvent *event)
{
m_clipboardHelper->clearClipboard();
event->accept();
}
/**
* @brief MainWindow::eventFilter filter out some events and focus the
* treeview
* @param obj
* @param event
* @return
*/
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == ui->lineEdit && event->type() == QEvent::KeyPress) {
auto *key = dynamic_cast<QKeyEvent *>(event);
if (key != nullptr && key->key() == Qt::Key_Down) {
ui->treeView->setFocus();
}
}
return QObject::eventFilter(obj, event);
}
/**
* @brief MainWindow::keyPressEvent did anyone press return, enter or escape?
* @param event
*/
void MainWindow::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Delete:
onDelete();
break;
case Qt::Key_Return:
case Qt::Key_Enter:
if (proxyModel.rowCount() > 0)
selectTreeItem(ui->treeView->currentIndex());
break;
case Qt::Key_Escape:
ui->lineEdit->clear();
break;
default:
break;
}
}
/**
* @brief MainWindow::showContextMenu show us the (file or folder) context
* menu
* @param pos
*/
void MainWindow::showContextMenu(const QPoint &pos)
{
QModelIndex index = ui->treeView->indexAt(pos);
bool selected = true;
if (!index.isValid()) {
ui->treeView->clearSelection();
ui->actionDelete->setEnabled(false);
ui->actionEdit->setEnabled(false);
selected = false;
}
ui->treeView->setCurrentIndex(index);
QPoint globalPos = ui->treeView->viewport()->mapToGlobal(pos);
QFileInfo fileOrFolder = model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
QMenu contextMenu;
if (!selected || fileOrFolder.isDir()) {
const QAction *addFolderAction = contextMenu.addAction(i18n("Add folder"));
const QAction *addPasswordAction = contextMenu.addAction(i18n("Add password"));
const QAction *usersAction = contextMenu.addAction(i18n("Users"));
connect(addFolderAction, &QAction::triggered, this, &MainWindow::addFolder);
connect(addPasswordAction, &QAction::triggered, this, &MainWindow::addPassword);
connect(usersAction, &QAction::triggered, this, &MainWindow::onUsers);
} else if (fileOrFolder.isFile()) {
const QAction *edit = contextMenu.addAction(i18n("Edit"));
connect(edit, &QAction::triggered, this, &MainWindow::onEdit);
}
if (selected) {
contextMenu.addSeparator();
if (fileOrFolder.isDir()) {
const QAction *renameFolderAction = contextMenu.addAction(i18n("Rename folder"));
connect(renameFolderAction, &QAction::triggered, this, &MainWindow::renameFolder);
} else if (fileOrFolder.isFile()) {
const QAction *renamePasswordAction = contextMenu.addAction(i18n("Rename password"));
connect(renamePasswordAction, &QAction::triggered, this, &MainWindow::renamePassword);
}
const QAction *deleteItem = contextMenu.addAction(i18n("Delete"));
connect(deleteItem, &QAction::triggered, this, &MainWindow::onDelete);
}
contextMenu.exec(globalPos);
}
/**
* @brief MainWindow::addFolder add a new folder to store passwords in
*/
void MainWindow::addFolder()
{
bool ok;
QString dir = directoryName(ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString());
if (dir.isEmpty()) {
dir = Settings::getPassStore();
}
QString newdir = QInputDialog::getText(this, i18n("New file"), i18n("New Folder: \n(Will be placed in %1 )", dir), QLineEdit::Normal, QString{}, &ok);
if (!ok || newdir.isEmpty())
return;
QDir(dir).mkdir(newdir);
}
/**
* @brief MainWindow::renameFolder rename an existing folder
*/
void MainWindow::renameFolder()
{
bool ok;
QString srcDir = QDir::cleanPath(directoryName(ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString()));
if (srcDir.isEmpty()) {
return;
}
QString srcDirName = QDir(srcDir).dirName();
QString newName = QInputDialog::getText(this, i18n("Rename file"), i18n("Rename Folder To: "), QLineEdit::Normal, srcDirName, &ok);
if (!ok || newName.isEmpty())
return;
QString destDir = srcDir;
destDir.replace(srcDir.lastIndexOf(srcDirName), srcDirName.length(), newName);
m_pass->Move(srcDir, destDir);
}
/**
* @brief MainWindow::editPassword read password and open edit window via
* MainWindow::onEdit()
*/
void MainWindow::editPassword(const QString &file)
{
if (!file.isEmpty()) {
setPassword(file, false);
}
}
/**
* @brief MainWindow::renamePassword rename an existing password
*/
void MainWindow::renamePassword()
{
bool ok;
QString file = ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString();
QString filePath = QFileInfo(file).path();
QString fileName = QFileInfo(file).fileName();
if (fileName.endsWith(QStringLiteral(".gpg"), Qt::CaseInsensitive))
fileName.chop(4);
QString newName = QInputDialog::getText(this, i18n("Rename file"), i18n("Rename File To: "), QLineEdit::Normal, fileName, &ok);
if (!ok || newName.isEmpty())
return;
QString newFile = QDir(filePath).filePath(newName);
m_pass->Move(file, newFile);
}
/**
* @brief MainWindow::clearTemplateWidgets empty the template widget fields in
* the UI
*/
void MainWindow::clearTemplateWidgets()
{
while (ui->contentLayout->count() > 0) {
ui->contentLayout->removeRow(ui->contentLayout->rowCount() -1);
}
}
/**
* @brief MainWindow::addToGridLayout add a field to the template grid
* @param position
* @param field
* @param value
*/
void MainWindow::addToGridLayout(const QString &field, const QString &value)
{
QString trimmedField = field.trimmed();
QString trimmedValue = value.trimmed();
// Combine the Copy button and the line edit in one widget
QFrame *frame = new QFrame();
QLayout *ly = new QHBoxLayout();
ly->setContentsMargins(5, 2, 2, 2);
ly->setSpacing(0);
frame->setLayout(ly);
auto fieldLabel = createPushButton(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy '%1' to clipboard", trimmedField), m_clipboardHelper, [this, trimmedValue] {
m_clipboardHelper->copyTextToClipboard(trimmedValue);
});
frame->layout()->addWidget(fieldLabel.release());
auto qrButton = createPushButton(QIcon::fromTheme(QStringLiteral("view-barcode-qr")), i18n("View '%1' QR Code", trimmedField), m_clipboardHelper, [this, trimmedValue]() {
auto barcode = Prison::Barcode::create(Prison::QRCode);
if (!barcode) {
return;
}
barcode->setData(trimmedValue);
auto image = barcode->toImage(barcode->preferredSize(window()->devicePixelRatioF()));
QDialog popup(nullptr, Qt::Popup | Qt::FramelessWindowHint);
QVBoxLayout *layout = new QVBoxLayout;
QLabel *popupLabel = new QLabel();
layout->addWidget(popupLabel);
popupLabel->setPixmap(QPixmap::fromImage(image));
popupLabel->setScaledContents(true);
popupLabel->show();
popup.setLayout(layout);
popup.move(QCursor::pos());
popup.exec();
});
frame->layout()->addWidget(qrButton.release());
if (trimmedField == i18n("Password")) {
auto *line = new QLineEdit();
line->setObjectName(trimmedField);
line->setText(trimmedValue);
line->setReadOnly(true);
line->setContentsMargins(0, 0, 0, 0);
line->setEchoMode(QLineEdit::Password);
auto icon = QIcon::fromTheme(QStringLiteral("password-show-on"));
icon.addFile(QStringLiteral("password-show-off"), QSize(), QIcon::Normal, QIcon::Off);
auto showButton = createPushButton(icon, i18n("Toggle password visibility"), line, [line]() {
if (line->echoMode() == QLineEdit::Password) {
line->setEchoMode(QLineEdit::Normal);
} else {
line->setEchoMode(QLineEdit::Password);
}
});
showButton->setCheckable(true);
showButton->setContentsMargins(0, 0, 0, 0);
frame->layout()->addWidget(showButton.release());
frame->layout()->addWidget(line);
} else {
auto *line = new QLabel();
line->setOpenExternalLinks(true);
line->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard);
line->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
line->setObjectName(trimmedField);
trimmedValue.replace(Util::protocolRegex(), QStringLiteral(R"(<a href="\1">\1</a>)"));
line->setText(trimmedValue);
line->setContentsMargins(5, 0, 0, 0);
frame->layout()->addWidget(line);
}
// set into the layout
ui->contentLayout->addRow(trimmedField, frame);
}
/**
* @brief MainWindow::startReencryptPath disable ui elements and treeview
*/
void MainWindow::startReencryptPath()
{
statusBar()->showMessage(i18n("Re-encrypting folders"), 3000);
setUiElementsEnabled(false);
ui->treeView->setDisabled(true);
}
/**
* @brief MainWindow::endReencryptPath re-enable ui elements
*/
void MainWindow::endReencryptPath()
{
setUiElementsEnabled(true);
}
/**
* @brief MainWindow::critical critical message popup wrapper.
* @param title
* @param msg
*/
void MainWindow::critical(const QString& title, const QString& msg)
{
QMessageBox::critical(this, title, msg);
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Dec 9, 1:17 AM (23 h, 45 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
bf/8e/c40453dd78d80b1eb5e53627bc53

Event Timeline