Page MenuHome GnuPG

No OneTemporary

diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 14f1403..ae4e9dc 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1,1473 +1,1477 @@
#include "mainwindow.h"
#include "debughelper.h"
#include <QClipboard>
#include <QCloseEvent>
#include <QDesktopServices>
#include <QFileInfo>
#include <QInputDialog>
#include <QLabel>
#include <QMenu>
#include <QMessageBox>
#include <QQueue>
#include <QShortcut>
#include <QTextCodec>
#ifdef Q_OS_WIN
#define WIN32_LEAN_AND_MEAN /*_KILLING_MACHINE*/
#define WIN32_EXTRA_LEAN
#include <windows.h>
#include <winnetwk.h>
#undef DELETE
#endif
#include "configdialog.h"
#include "filecontent.h"
#include "keygendialog.h"
#include "passworddialog.h"
#include "qpushbuttonwithclipboard.h"
#include "qtpasssettings.h"
#include "settingsconstants.h"
#include "trayicon.h"
#include "ui_mainwindow.h"
#include "usersdialog.h"
#include "util.h"
/**
* @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(const QString &searchText, QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow), fusedav(this),
clippedText(QString()), freshStart(true), keygen(NULL),
startupPhase(true), tray(NULL) {
#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);
// i think this should be moved out of MainWindow (in main.cpp as example)
if (!checkConfig()) {
// no working config so this should quit without config anything
QApplication::quit();
}
// register shortcut ctrl/cmd + Q to close the main window
new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this, SLOT(close()));
// register shortcut ctrl/cmd + C to copy the currently selected password
new QShortcut(QKeySequence(QKeySequence::StandardKey::Copy), this,
SLOT(copyPasswordFromTreeview()));
// TODO(bezet): this should be reconnected dynamically when pass changes
connectPassSignalHandlers(QtPassSettings::getRealPass());
connectPassSignalHandlers(QtPassSettings::getImitatePass());
// only for ipass
connect(QtPassSettings::getImitatePass(), SIGNAL(startReencryptPath()), this,
SLOT(startReencryptPath()));
connect(QtPassSettings::getImitatePass(), SIGNAL(endReencryptPath()), this,
SLOT(endReencryptPath()));
clearPanelTimer.setSingleShot(true);
connect(&clearPanelTimer, SIGNAL(timeout()), this, SLOT(clearPanel()));
clearClipboardTimer.setSingleShot(true);
connect(&clearClipboardTimer, SIGNAL(timeout()), this,
SLOT(clearClipboard()));
initToolBarButtons();
initStatusBar();
#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
ui->lineEdit->setClearButtonEnabled(true);
#endif
enableUiElements(true);
qsrand(static_cast<uint>(QTime::currentTime().msec()));
QTimer::singleShot(10, this, SLOT(focusInput()));
ui->lineEdit->setText(searchText);
}
/**
* @brief MainWindow::initToolBarButtons init main ToolBar and connect actions
*/
void MainWindow::initToolBarButtons() {
connect(ui->actionAddPassword, SIGNAL(triggered()), this,
SLOT(addPassword()));
connect(ui->actionAddFolder, SIGNAL(triggered()), this, SLOT(addFolder()));
connect(ui->actionEdit, SIGNAL(triggered()), this, SLOT(onEdit()));
connect(ui->actionDelete, SIGNAL(triggered()), this, SLOT(onDelete()));
connect(ui->actionPush, SIGNAL(triggered()), this, SLOT(onPush()));
connect(ui->actionUpdate, SIGNAL(triggered()), this, SLOT(onUpdate()));
connect(ui->actionUsers, SIGNAL(triggered()), this, SLOT(onUsers()));
connect(ui->actionConfig, SIGNAL(triggered()), this, SLOT(onConfig()));
connect(ui->actionOtp, SIGNAL(triggered()), this, SLOT(onOtp()));
ui->actionAddPassword->setIcon(
QIcon::fromTheme("document-new", QIcon(":/icons/document-new.svg")));
ui->actionAddFolder->setIcon(
QIcon::fromTheme("folder-new", QIcon(":/icons/folder-new.svg")));
ui->actionEdit->setIcon(QIcon::fromTheme(
"document-properties", QIcon(":/icons/document-properties.svg")));
ui->actionDelete->setIcon(
QIcon::fromTheme("edit-delete", QIcon(":/icons/edit-delete.svg")));
ui->actionPush->setIcon(
QIcon::fromTheme("go-up", QIcon(":/icons/go-top.svg")));
ui->actionUpdate->setIcon(
QIcon::fromTheme("go-down", QIcon(":/icons/go-bottom.svg")));
ui->actionUsers->setIcon(QIcon::fromTheme(
"x-office-address-book", QIcon(":/icons/x-office-address-book.svg")));
ui->actionConfig->setIcon(QIcon::fromTheme(
"applications-system", QIcon(":/icons/applications-system.svg")));
}
/**
* @brief MainWindow::initStatusBar init statusBar with default message and logo
*/
void MainWindow::initStatusBar() {
ui->statusBar->showMessage(tr("Welcome to QtPass %1").arg(VERSION), 2000);
QPixmap logo = QPixmap::fromImage(QImage(":/artwork/icon.svg"))
.scaledToHeight(statusBar()->height());
QLabel *logoApp = new QLabel(statusBar());
logoApp->setPixmap(logo);
statusBar()->addPermanentWidget(logoApp);
}
/**
* @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::~MainWindow destroy!
*/
MainWindow::~MainWindow() {
#ifdef Q_OS_WIN
if (QtPassSettings::isUseWebDav())
WNetCancelConnection2A(QtPassSettings::getPassStore().toUtf8().constData(),
0, 1);
#else
if (fusedav.state() == QProcess::Running) {
fusedav.terminate();
fusedav.waitForFinished(2000);
}
#endif
}
/**
* @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 (this->isActiveWindow()) {
focusInput();
}
}
}
/**
* @brief MainWindow::connectPassSignalHandlers this method connects Pass
* signals to approprite MainWindow
* slots
*
* @param pass pointer to pass instance
*/
void MainWindow::connectPassSignalHandlers(Pass *pass) {
// TODO(bezet): this is never emitted(should be), also naming(see
// critical())
connect(pass, &Pass::error, this, &MainWindow::processError);
connect(pass, &Pass::startingExecuteWrapper, this,
&MainWindow::executeWrapperStarted);
connect(pass, &Pass::critical, this, &MainWindow::critical);
connect(pass, &Pass::statusMsg, this, &MainWindow::showStatusMessage);
connect(pass, &Pass::processErrorExit, this, &MainWindow::processErrorExit);
connect(pass, &Pass::finishedGitInit, this, &MainWindow::passStoreChanged);
connect(pass, &Pass::finishedGitPull, this, &MainWindow::processFinished);
connect(pass, &Pass::finishedGitPush, this, &MainWindow::processFinished);
connect(pass, &Pass::finishedShow, this, &MainWindow::passShowHandler);
connect(pass, &Pass::finishedOtpGenerate, this, &MainWindow::passOtpHandler);
connect(pass, &Pass::finishedInsert, this, &MainWindow::finishedInsert);
connect(pass, &Pass::finishedRemove, this, &MainWindow::passStoreChanged);
connect(pass, &Pass::finishedInit, this, &MainWindow::passStoreChanged);
connect(pass, &Pass::finishedMove, this, &MainWindow::passStoreChanged);
connect(pass, &Pass::finishedCopy, this, &MainWindow::passStoreChanged);
connect(pass, &Pass::finishedGenerateGPGKeys, this,
&MainWindow::keyGenerationComplete);
}
/**
* @brief MainWindow::mountWebDav is some scary voodoo magic
*/
void MainWindow::mountWebDav() {
#ifdef Q_OS_WIN
char dst[20] = {0};
NETRESOURCEA netres;
memset(&netres, 0, sizeof(netres));
netres.dwType = RESOURCETYPE_DISK;
netres.lpLocalName = 0;
netres.lpRemoteName = QtPassSettings::getWebDavUrl().toUtf8().data();
DWORD size = sizeof(dst);
DWORD r = WNetUseConnectionA(
reinterpret_cast<HWND>(effectiveWinId()), &netres,
QtPassSettings::getWebDavPassword().toUtf8().constData(),
QtPassSettings::getWebDavUser().toUtf8().constData(),
CONNECT_TEMPORARY | CONNECT_INTERACTIVE | CONNECT_REDIRECT, dst, &size,
0);
if (r == NO_ERROR) {
QtPassSettings::setPassStore(dst);
} else {
char message[256] = {0};
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, r, 0, message,
sizeof(message), 0);
ui->textBrowser->setTextColor(Qt::red);
ui->textBrowser->setText(tr("Failed to connect WebDAV:\n") + message +
" (0x" + QString::number(r, 16) + ")");
ui->textBrowser->setTextColor(Qt::black);
}
#else
fusedav.start("fusedav -o nonempty -u \"" + QtPassSettings::getWebDavUser() +
"\" " + QtPassSettings::getWebDavUrl() + " \"" +
QtPassSettings::getPassStore() + '"');
fusedav.waitForStarted();
if (fusedav.state() == QProcess::Running) {
QString pwd = QtPassSettings::getWebDavPassword();
bool ok = true;
if (pwd.isEmpty()) {
pwd = QInputDialog::getText(this, tr("QtPass WebDAV password"),
tr("Enter password to connect to WebDAV:"),
QLineEdit::Password, "", &ok);
}
if (ok && !pwd.isEmpty()) {
fusedav.write(pwd.toUtf8() + '\n');
fusedav.closeWriteChannel();
fusedav.waitForFinished(2000);
} else {
fusedav.terminate();
}
}
QString error = fusedav.readAllStandardError();
int prompt = error.indexOf("Password:");
if (prompt >= 0)
error.remove(0, prompt + 10);
if (fusedav.state() != QProcess::Running)
error = tr("fusedav exited unexpectedly\n") + error;
if (error.size() > 0) {
ui->textBrowser->setTextColor(Qt::red);
ui->textBrowser->setText(
tr("Failed to start fusedav to connect WebDAV:\n") + error);
ui->textBrowser->setTextColor(Qt::black);
}
#endif
}
/**
* @brief MainWindow::checkConfig make sure we are ready to go as soon as
* possible
*/
bool MainWindow::checkConfig() {
QString version = QtPassSettings::getVersion();
// if (freshStart) {
restoreWindow();
//}
QString passStore = QtPassSettings::getPassStore(Util::findPasswordStore());
QtPassSettings::setPassStore(passStore);
QtPassSettings::initExecutables();
if (QtPassSettings::isAlwaysOnTop()) {
Qt::WindowFlags flags = windowFlags();
this->setWindowFlags(flags | Qt::WindowStaysOnTopHint);
this->show();
}
if (QtPassSettings::isUseTrayIcon() && tray == NULL) {
initTrayIcon();
if (freshStart && QtPassSettings::isStartMinimized()) {
// since we are still in constructor, can't directly hide
QTimer::singleShot(10, this, SLOT(hide()));
}
} /*else if (!QtPassSettings::isUseTrayIcon() && tray != NULL) {
destroyTrayIcon();
}*/
// dbg()<< version;
// Config updates
if (version.isEmpty()) {
dbg() << "assuming fresh install";
if (QtPassSettings::getAutoclearSeconds() < 5)
QtPassSettings::setAutoclearSeconds(10);
if (QtPassSettings::getAutoclearPanelSeconds() < 5)
QtPassSettings::setAutoclearPanelSeconds(10);
if (!QtPassSettings::getPwgenExecutable().isEmpty())
QtPassSettings::setUsePwgen(true);
else
QtPassSettings::setUsePwgen(false);
QtPassSettings::setPassTemplate("login\nurl");
} else {
// QStringList ver = version.split(".");
// dbg()<< ver;
// if (ver[0] == "0" && ver[1] == "8") {
//// upgrade to 0.9
// }
if (QtPassSettings::getPassTemplate().isEmpty())
QtPassSettings::setPassTemplate("login\nurl");
}
QtPassSettings::setVersion(VERSION);
if (Util::checkConfig()) {
config();
if (freshStart && Util::checkConfig())
return false;
}
freshStart = false;
// TODO(annejan): this needs to be before we try to access the store,
// but it would be better to do it after the Window is shown,
// as the long delay it can cause is irritating otherwise.
if (QtPassSettings::isUseWebDav())
mountWebDav();
model.setNameFilters(QStringList() << "*.gpg");
model.setNameFilterDisables(false);
proxyModel.setSourceModel(&model);
proxyModel.setModelAndStore(&model, passStore);
selectionModel.reset(new QItemSelectionModel(&proxyModel));
model.fetchMore(model.setRootPath(passStore));
model.sort(0, Qt::AscendingOrder);
ui->treeView->setModel(&proxyModel);
ui->treeView->setRootIndex(
proxyModel.mapFromSource(model.setRootPath(passStore)));
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);
connect(ui->treeView, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(showContextMenu(const QPoint &)));
connect(ui->treeView, SIGNAL(emptyClicked()), this, SLOT(deselect()));
ui->textBrowser->setOpenExternalLinks(true);
ui->textBrowser->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->textBrowser, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(showBrowserContextMenu(const QPoint &)));
updateProfileBox();
QtPassSettings::getPass()->updateEnv();
clearPanelTimer.setInterval(1000 *
QtPassSettings::getAutoclearPanelSeconds());
clearClipboardTimer.setInterval(1000 * QtPassSettings::getAutoclearSeconds());
updateGitButtonVisibility();
updateOtpButtonVisibility();
startupPhase = false;
return true;
}
/**
* @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);
// Automatically default to pass if it's available
if (freshStart && QFile(QtPassSettings::getPassExecutable()).exists()) {
QtPassSettings::setUsePass(true);
}
if (startupPhase)
d->wizard(); // does shit
if (d->exec()) {
if (d->result() == QDialog::Accepted) {
if (QtPassSettings::isAlwaysOnTop()) {
Qt::WindowFlags flags = windowFlags();
this->setWindowFlags(flags | Qt::WindowStaysOnTopHint);
} else {
this->setWindowFlags(Qt::Window);
}
this->show();
updateProfileBox();
- ui->treeView->setRootIndex(proxyModel.mapFromSource(
- model.setRootPath(QtPassSettings::getPassStore())));
+ // For freshStart, proxyModel is not yet configured
+ // and maniplating it will assert
+ if (!freshStart) {
+ ui->treeView->setRootIndex(proxyModel.mapFromSource(
+ model.setRootPath(QtPassSettings::getPassStore())));
+ }
if (freshStart && Util::checkConfig())
config();
QtPassSettings::getPass()->updateEnv();
clearPanelTimer.setInterval(1000 *
QtPassSettings::getAutoclearPanelSeconds());
clearClipboardTimer.setInterval(1000 *
QtPassSettings::getAutoclearSeconds());
updateGitButtonVisibility();
updateOtpButtonVisibility();
if (QtPassSettings::isUseTrayIcon() && tray == NULL)
initTrayIcon();
else if (!QtPassSettings::isUseTrayIcon() && tray != NULL) {
destroyTrayIcon();
}
}
freshStart = false;
}
}
/**
* @brief MainWindow::onUpdate do a git pull
*/
void MainWindow::onUpdate(bool block) {
ui->statusBar->showMessage(tr("Updating password-store"), 2000);
if (block)
QtPassSettings::getPass()->GitPull_b();
else
QtPassSettings::getPass()->GitPull();
}
/**
* @brief MainWindow::onPush do a git push
*/
void MainWindow::onPush() {
if (QtPassSettings::isUseGit()) {
ui->statusBar->showMessage(tr("Updating password-store"), 2000);
QtPassSettings::getPass()->GitPush();
}
}
/**
* @brief MainWindow::getFile get the selected file path
* @param index
* @param forPass returns relative path without '.gpg' extension
* @return path
* @return
*/
QString MainWindow::getFile(const QModelIndex &index, bool forPass) {
if (!index.isValid() ||
!model.fileInfo(proxyModel.mapToSource(index)).isFile())
return QString();
QString filePath = model.filePath(proxyModel.mapToSource(index));
if (forPass) {
filePath = QDir(QtPassSettings::getPassStore()).relativeFilePath(filePath);
filePath.replace(QRegExp("\\.gpg$"), "");
}
return filePath;
}
/**
* @brief MainWindow::on_treeView_clicked read the selected password file
* @param index
*/
void MainWindow::on_treeView_clicked(const QModelIndex &index) {
bool cleared = ui->treeView->currentIndex().flags() == Qt::NoItemFlags;
currentDir =
Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
// TODO(bezet): "Could not decrypt";
clippedText = "";
QString file = getFile(index, true);
ui->passwordName->setText(getFile(index, true));
if (!file.isEmpty() && !cleared) {
QtPassSettings::getPass()->Show(file);
} else {
clearPanel(false);
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::on_treeView_doubleClicked(const QModelIndex &index) {
QFileInfo fileOrFolder =
model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
if (fileOrFolder.isFile()) {
editPassword(getFile(index, true));
}
}
/**
* @brief MainWindow::deselect clear the selection, password and copy buffer
*/
void MainWindow::deselect() {
currentDir = "/";
clearClipboard();
ui->passwordName->setText("");
clearPanel(false);
}
/**
* @brief MainWindow::executePassGitInit git init wrapper
*/
void MainWindow::executePassGitInit() {
dbg() << "Pass git init called";
QtPassSettings::getPass()->GitInit();
}
void MainWindow::executeWrapperStarted() {
clearTemplateWidgets();
ui->textBrowser->clear();
enableUiElements(false);
clearPanelTimer.stop();
}
void MainWindow::keyGenerationComplete(const QString &p_output,
const QString &p_errout) {
// qDebug() << p_output;
// qDebug() << p_errout;
if (0 != keygen) {
qDebug() << "Keygen Done";
keygen->close();
keygen = 0;
// TODO(annejan) some sanity checking ?
}
processFinished(p_output, p_errout);
}
void MainWindow::passShowHandler(const QString &p_output) {
QStringList templ = QtPassSettings::isUseTemplate()
? QtPassSettings::getPassTemplate().split("\n")
: QStringList();
bool allFields =
QtPassSettings::isUseTemplate() && QtPassSettings::isTemplateAllFields();
FileContent fileContent = FileContent::parse(p_output, templ, allFields);
QString output = p_output;
QString password = fileContent.getPassword();
// handle clipboard
if (QtPassSettings::getClipBoardType() != Enums::CLIPBOARD_NEVER &&
!p_output.isEmpty()) {
clippedText = password;
if (QtPassSettings::getClipBoardType() == Enums::CLIPBOARD_ALWAYS)
copyTextToClipboard(password);
}
// first clear the current view:
clearTemplateWidgets();
// show what is needed:
if (QtPassSettings::isHideContent()) {
output = "***" + tr("Content hidden") + "***";
} else {
if (!password.isEmpty()) {
// set the password, it is hidden if needed in addToGridLayout
addToGridLayout(0, tr("Password"), password);
}
NamedValues namedValues = fileContent.getNamedValues();
for (int j = 0; j < namedValues.length(); ++j) {
NamedValue nv = namedValues.at(j);
addToGridLayout(j + 1, nv.name, nv.value);
}
if (ui->gridLayout->count() == 0)
ui->verticalLayoutPassword->setSpacing(0);
else
ui->verticalLayoutPassword->setSpacing(6);
output = fileContent.getRemainingData();
}
if (QtPassSettings::isUseAutoclearPanel()) {
clearPanelTimer.start();
}
DisplayInTextBrowser(output);
enableUiElements(true);
}
void MainWindow::passOtpHandler(const QString &p_output) {
if (!p_output.isEmpty()) {
addToGridLayout(ui->gridLayout->count() + 1, tr("OTP Code"), p_output);
copyTextToClipboard(p_output);
}
if (QtPassSettings::isUseAutoclearPanel()) {
clearPanelTimer.start();
}
enableUiElements(true);
}
void MainWindow::passStoreChanged(const QString &p_out, const QString &p_err) {
processFinished(p_out, p_err);
doGitPush();
}
void MainWindow::doGitPush() {
if (QtPassSettings::isAutoPush())
onPush();
}
void MainWindow::finishedInsert(const QString &p_output,
const QString &p_errout) {
processFinished(p_output, p_errout);
doGitPush();
on_treeView_clicked(ui->treeView->currentIndex());
}
void MainWindow::DisplayInTextBrowser(QString output, QString prefix,
QString postfix) {
output.replace(QRegExp("<"), "&lt;");
output.replace(QRegExp(">"), "&gt;");
output.replace(QRegExp(" "), "&nbsp;");
output.replace(
QRegExp("((?:https?|ftp|ssh|sftp|ftps|webdav|webdavs)://\\S+)"),
"<a href=\"\\1\">\\1</a>");
output.replace(QRegExp("\n"), "<br />");
output = prefix + output + postfix;
if (!ui->textBrowser->toPlainText().isEmpty())
output = ui->textBrowser->toHtml() + output;
ui->textBrowser->setHtml(output);
}
void MainWindow::processErrorExit(int exitCode, const QString &p_error) {
if (!p_error.isEmpty()) {
QString output;
QString error = p_error;
error.replace(QRegExp("<"), "&lt;");
error.replace(QRegExp(">"), "&gt;");
error.replace(QRegExp(" "), "&nbsp;");
if (exitCode == 0) {
// https://github.com/IJHack/qtpass/issues/111
output = "<span style=\"color: darkgray;\">" + error + "</span><br />";
} else {
output = "<span style=\"color: red;\">" + error + "</span><br />";
}
output.replace(
QRegExp("((?:https?|ftp|ssh|sftp|ftps|webdav|webdavs)://\\S+)"),
"<a href=\"\\1\">\\1</a>");
output.replace(QRegExp("\n"), "<br />");
if (!ui->textBrowser->toPlainText().isEmpty())
output = ui->textBrowser->toHtml() + output;
ui->textBrowser->setHtml(output);
}
enableUiElements(true);
}
/**
* @brief MainWindow::clearClipboard remove clipboard contents.
*/
void MainWindow::clearClipboard() {
QClipboard *clipboard = QApplication::clipboard();
bool cleared = false;
if (this->clippedText == clipboard->text(QClipboard::Selection)) {
clipboard->clear(QClipboard::Clipboard);
cleared = true;
}
if (this->clippedText == clipboard->text(QClipboard::Clipboard)) {
clipboard->clear(QClipboard::Clipboard);
cleared = true;
}
if (cleared) {
ui->statusBar->showMessage(tr("Clipboard cleared"), 2000);
} else {
ui->statusBar->showMessage(tr("Clipboard not cleared"), 2000);
}
this->clippedText.clear();
}
/**
* @brief MainWindow::clearPanel hide the information from shoulder surfers
*/
void MainWindow::clearPanel(bool notify) {
while (ui->gridLayout->count() > 0) {
QLayoutItem *item = ui->gridLayout->takeAt(0);
delete item->widget();
delete item;
}
if (notify) {
QString output = "***" + tr("Password and Content hidden") + "***";
ui->textBrowser->setHtml(output);
} else {
ui->textBrowser->setHtml("");
}
}
/**
* @brief MainWindow::processFinished background process has finished
* @param exitCode
* @param exitStatus
* @param output stdout from a process
* @param errout stderr from a process
*/
void MainWindow::processFinished(const QString &p_output,
const QString &p_errout) {
DisplayInTextBrowser(p_output);
// Sometimes there is error output even with 0 exit code, which is
// assumed in this function
processErrorExit(0, p_errout);
enableUiElements(true);
}
/**
* @brief MainWindow::enableUiElements enable or disable the relevant UI
* elements
* @param state
*/
void MainWindow::enableUiElements(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);
updateGitButtonVisibility();
updateOtpButtonVisibility();
}
void MainWindow::restoreWindow() {
QByteArray geometry = QtPassSettings::getGeometry(saveGeometry());
restoreGeometry(geometry);
QByteArray savestate = QtPassSettings::getSavestate(saveState());
restoreState(savestate);
QPoint position = QtPassSettings::getPos(pos());
move(position);
QSize newSize = QtPassSettings::getSize(size());
resize(newSize);
if (QtPassSettings::isMaximized(isMaximized())) {
showMaximized();
}
}
/**
* @brief MainWindow::processError something went wrong
* @param error
*/
void MainWindow::processError(QProcess::ProcessError error) {
QString errorString;
switch (error) {
case QProcess::FailedToStart:
errorString = tr("QProcess::FailedToStart");
break;
case QProcess::Crashed:
errorString = tr("QProcess::Crashed");
break;
case QProcess::Timedout:
errorString = tr("QProcess::Timedout");
break;
case QProcess::ReadError:
errorString = tr("QProcess::ReadError");
break;
case QProcess::WriteError:
errorString = tr("QProcess::WriteError");
break;
case QProcess::UnknownError:
errorString = tr("QProcess::UnknownError");
break;
}
ui->textBrowser->setTextColor(Qt::red);
ui->textBrowser->setText(errorString);
ui->textBrowser->setTextColor(Qt::black);
enableUiElements(true);
}
/**
* @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::on_lineEdit_textChanged(const QString &arg1) {
ui->treeView->expandAll();
ui->statusBar->showMessage(tr("Looking for: %1").arg(arg1), 1000);
QString query = arg1;
query.replace(QRegExp(" "), ".*");
QRegExp regExp(query, Qt::CaseInsensitive);
proxyModel.setFilterRegExp(regExp);
ui->treeView->setRootIndex(proxyModel.mapFromSource(
model.setRootPath(QtPassSettings::getPassStore())));
selectFirstFile();
}
/**
* @brief MainWindow::on_lineEdit_returnPressed get searching
*
* Select the first possible file in the tree
*/
void MainWindow::on_lineEdit_returnPressed() {
dbg() << "on_lineEdit_returnPressed";
selectFirstFile();
on_treeView_clicked(ui->treeView->currentIndex());
}
/**
* @brief MainWindow::selectFirstFile select the first possible file in the
* tree
*/
void MainWindow::selectFirstFile() {
QModelIndex index = proxyModel.mapFromSource(
model.setRootPath(QtPassSettings::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(file, isNew, this);
connect(QtPassSettings::getPass(), &Pass::finishedShow, &d,
&PasswordDialog::setPass);
if (!d.exec()) {
this->ui->treeView->setFocus();
}
}
/**
* @brief MainWindow::addPassword add a new password by showing a
* number of dialogs.
*/
void MainWindow::addPassword() {
bool ok;
QString dir =
Util::getDir(ui->treeView->currentIndex(), true, model, proxyModel);
QString file =
QInputDialog::getText(this, tr("New file"),
tr("New password file: \n(Will be placed in %1 )")
.arg(QtPassSettings::getPassStore() +
Util::getDir(ui->treeView->currentIndex(),
true, model, proxyModel)),
QLineEdit::Normal, "", &ok);
if (!ok || file.isEmpty())
return;
file = dir + file;
setPassword(file);
}
/**
* @brief MainWindow::onDelete remove password, if you are
* sure.
*/
void MainWindow::onDelete() {
QFileInfo fileOrFolder =
model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
QString file = "";
bool isDir = false;
if (fileOrFolder.isFile()) {
file = getFile(ui->treeView->currentIndex(), true);
} else {
file = Util::getDir(ui->treeView->currentIndex(), true, model, proxyModel);
isDir = true;
}
QString dirMessage = tr(" and the whole content?");
if (isDir) {
QDirIterator it(model.rootPath() + "/" + file,
QDirIterator::Subdirectories);
bool okDir = true;
while (it.hasNext() && okDir) {
it.next();
if (QFileInfo(it.filePath()).isFile()) {
if (QFileInfo(it.filePath()).suffix() != "gpg") {
okDir = false;
dirMessage = tr(" and the whole content? <br><strong>Attention: "
"there are unexpected files in the given folder, "
"check them before continue.</strong>");
}
}
}
}
if (QMessageBox::question(
this, isDir ? tr("Delete folder?") : tr("Delete password?"),
tr("Are you sure you want to delete %1%2")
.arg(QDir::separator() + file)
.arg(isDir ? dirMessage : "?"),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
QtPassSettings::getPass()->Remove(file, isDir);
}
/**
* @brief MainWindow::onOTP try and generate (selected) OTP code.
*/
void MainWindow::onOtp() {
QString file = getFile(ui->treeView->currentIndex(), true);
if (!file.isEmpty()) {
if (QtPassSettings::isUseOtp())
QtPassSettings::getPass()->OtpGenerate(file);
}
}
/**
* @brief MainWindow::onEdit try and edit (selected) password.
*/
void MainWindow::onEdit() {
QString file = getFile(ui->treeView->currentIndex(), true);
editPassword(file);
}
/**
* @brief MainWindow::userDialog see MainWindow::onUsers()
* @param dir folder to edit users for.
*/
void MainWindow::userDialog(QString dir) {
if (!dir.isEmpty())
currentDir = dir;
onUsers();
}
/**
* @brief MainWindow::onUsers edit users for the current
* folder,
* gets lists and opens UserDialog.
*/
void MainWindow::onUsers() {
QList<UserInfo> users = QtPassSettings::getPass()->listKeys();
if (users.size() == 0) {
QMessageBox::critical(this, tr("Can not get key list"),
tr("Unable to get list of available gpg keys"));
return;
}
QList<UserInfo> secret_keys = QtPassSettings::getPass()->listKeys("", true);
foreach (const UserInfo &sec, secret_keys) {
for (QList<UserInfo>::iterator it = users.begin(); it != users.end(); ++it)
if (sec.key_id == it->key_id)
it->have_secret = true;
}
QList<UserInfo> selected_users;
QString dir =
currentDir.isEmpty()
? Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel)
: currentDir;
int count = 0;
QStringList recipients = QtPassSettings::getPass()->getRecipientString(
dir.isEmpty() ? "" : dir, " ", &count);
if (!recipients.isEmpty())
selected_users = QtPassSettings::getPass()->listKeys(recipients);
foreach (const UserInfo &sel, selected_users) {
for (QList<UserInfo>::iterator it = users.begin(); it != users.end(); ++it)
if (sel.key_id == it->key_id)
it->enabled = true;
}
if (count > selected_users.size()) {
// Some keys seem missing from keyring, add them separately
QStringList recipients =
QtPassSettings::getPass()->getRecipientList(dir.isEmpty() ? "" : dir);
foreach (const QString recipient, recipients) {
if (QtPassSettings::getPass()->listKeys(recipient).size() < 1) {
UserInfo i;
i.enabled = true;
i.key_id = recipient;
i.name = " ?? " + tr("Key not found in keyring");
users.append(i);
}
}
}
UsersDialog d(this);
d.setUsers(&users);
if (!d.exec()) {
d.setUsers(NULL);
return;
}
d.setUsers(NULL);
QtPassSettings::getPass()->Init(dir, users);
}
/**
* @brief MainWindow::messageAvailable we have some text/message/search to do.
* @param message
*/
void MainWindow::messageAvailable(QString message) {
if (message.isEmpty()) {
focusInput();
} else {
ui->treeView->expandAll();
ui->lineEdit->setText(message);
on_lineEdit_returnPressed();
}
show();
raise();
}
/**
* @brief MainWindow::getSecretKeys get list of secret/private keys
* @return QStringList keys
*/
QStringList MainWindow::getSecretKeys() {
QList<UserInfo> keys = QtPassSettings::getPass()->listKeys("", true);
QStringList names;
if (keys.size() == 0)
return names;
foreach (const UserInfo &sec, keys)
names << sec.name;
return names;
}
/**
* @brief MainWindow::generateKeyPair internal gpg keypair generator . .
* @param batch
* @param keygenWindow
*/
void MainWindow::generateKeyPair(QString batch, QDialog *keygenWindow) {
keygen = keygenWindow;
ui->statusBar->showMessage(tr("Generating GPG key pair"), 60000);
QtPassSettings::getPass()->GenerateGPGKeys(batch);
}
/**
* @brief MainWindow::updateProfileBox update the list of profiles, optionally
* select a more appropriate one to view too
*/
void MainWindow::updateProfileBox() {
QHash<QString, QString> profiles = QtPassSettings::getProfiles();
if (profiles.isEmpty()) {
ui->profileWidget->hide();
} else {
ui->profileWidget->show();
ui->profileBox->setEnabled(profiles.size() > 1);
ui->profileBox->clear();
QHashIterator<QString, QString> i(profiles);
while (i.hasNext()) {
i.next();
if (!i.key().isEmpty())
ui->profileBox->addItem(i.key());
}
}
int index = ui->profileBox->findText(QtPassSettings::getProfile());
if (index != -1) // -1 for not found
ui->profileBox->setCurrentIndex(index);
}
/**
* @brief MainWindow::on_profileBox_currentIndexChanged make sure we show the
* correct "profile"
* @param name
*/
void MainWindow::on_profileBox_currentIndexChanged(QString name) {
if (startupPhase || name == QtPassSettings::getProfile())
return;
QtPassSettings::setProfile(name);
QtPassSettings::setPassStore(QtPassSettings::getProfiles()[name]);
ui->statusBar->showMessage(tr("Profile changed to %1").arg(name), 2000);
QtPassSettings::getPass()->updateEnv();
ui->treeView->setRootIndex(proxyModel.mapFromSource(
model.setRootPath(QtPassSettings::getPassStore())));
}
/**
* @brief MainWindow::initTrayIcon show a nice tray icon on systems that
* support
* it
*/
void MainWindow::initTrayIcon() {
this->tray = new TrayIcon(this);
// Setup tray icon
if (tray == NULL)
dbg() << "Allocating tray icon failed.";
if (!tray->getIsAllocated()) {
destroyTrayIcon();
}
}
/**
* @brief MainWindow::destroyTrayIcon remove that pesky tray icon
*/
void MainWindow::destroyTrayIcon() {
delete this->tray;
tray = NULL;
}
/**
* @brief MainWindow::closeEvent hide or quit
* @param event
*/
void MainWindow::closeEvent(QCloseEvent *event) {
if (QtPassSettings::isHideOnClose()) {
this->hide();
event->ignore();
} else {
clearClipboard();
QtPassSettings::setGeometry(saveGeometry());
QtPassSettings::setSavestate(saveState());
QtPassSettings::setMaximized(isMaximized());
if (!isMaximized()) {
QtPassSettings::setPos(pos());
QtPassSettings::setSize(size());
}
// QtPassSettings::setSplitterLeft(ui->splitter->sizes()[0]);
// QtPassSettings::setSplitterRight(ui->splitter->sizes()[1]);
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) {
QKeyEvent *key = static_cast<QKeyEvent *>(event);
if (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:
on_treeView_clicked(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);
currentDir = "";
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()) {
QAction *openFolder =
contextMenu.addAction(tr("Open folder with file manager"));
QAction *addFolder = contextMenu.addAction(tr("Add folder"));
QAction *addPassword = contextMenu.addAction(tr("Add password"));
QAction *users = contextMenu.addAction(tr("Users"));
connect(openFolder, SIGNAL(triggered()), this, SLOT(openFolder()));
connect(addFolder, SIGNAL(triggered()), this, SLOT(addFolder()));
connect(addPassword, SIGNAL(triggered()), this, SLOT(addPassword()));
connect(users, SIGNAL(triggered()), this, SLOT(onUsers()));
} else if (fileOrFolder.isFile()) {
QAction *edit = contextMenu.addAction(tr("Edit"));
connect(edit, SIGNAL(triggered()), this, SLOT(onEdit()));
}
if (selected) {
// if (useClipboard != CLIPBOARD_NEVER) {
// contextMenu.addSeparator();
// QAction* copyItem = contextMenu.addAction(tr("Copy Password"));
// if (getClippedPassword().length() == 0) copyItem->setEnabled(false);
// connect(copyItem, SIGNAL(triggered()), this,
// SLOT(copyPasswordToClipboard()));
// }
contextMenu.addSeparator();
QAction *deleteItem = contextMenu.addAction(tr("Delete"));
connect(deleteItem, SIGNAL(triggered()), this, SLOT(onDelete()));
}
contextMenu.exec(globalPos);
}
/**
* @brief MainWindow::showBrowserContextMenu show us the context menu in
* password window
* @param pos
*/
void MainWindow::showBrowserContextMenu(const QPoint &pos) {
QMenu *contextMenu = ui->textBrowser->createStandardContextMenu(pos);
QPoint globalPos = ui->textBrowser->viewport()->mapToGlobal(pos);
contextMenu->exec(globalPos);
}
/**
* @brief MainWindow::openFolder open the folder in the default file manager
*/
void MainWindow::openFolder() {
QString dir =
Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
QString path = QDir::toNativeSeparators(dir);
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}
/**
* @brief MainWindow::addFolder add a new folder to store passwords in
*/
void MainWindow::addFolder() {
bool ok;
QString dir =
Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
QString newdir =
QInputDialog::getText(this, tr("New file"),
tr("New Folder: \n(Will be placed in %1 )")
.arg(QtPassSettings::getPassStore() +
Util::getDir(ui->treeView->currentIndex(),
true, model, proxyModel)),
QLineEdit::Normal, "", &ok);
if (!ok || newdir.isEmpty())
return;
newdir.prepend(dir);
// dbg()<< newdir;
QDir().mkdir(newdir);
}
/**
* @brief MainWindow::editPassword read password and open edit window via
* MainWindow::onEdit()
*/
void MainWindow::editPassword(const QString &file) {
if (!file.isEmpty()) {
if (QtPassSettings::isUseGit() && QtPassSettings::isAutoPull())
onUpdate(true);
setPassword(file, false);
}
}
/**
* @brief MainWindow::clearTemplateWidgets empty the template widget fields in
* the UI
*/
void MainWindow::clearTemplateWidgets() {
while (ui->gridLayout->count() > 0) {
QLayoutItem *item = ui->gridLayout->takeAt(0);
delete item->widget();
delete item;
}
ui->verticalLayoutPassword->setSpacing(0);
}
/**
* @brief MainWindow::copyTextToClipboard copies text to your clipboard
* @param text
*/
void MainWindow::copyTextToClipboard(const QString &text) {
QClipboard *clip = QApplication::clipboard();
if (!QtPassSettings::isUseSelection()) {
clip->setText(text, QClipboard::Clipboard);
} else {
clip->setText(text, QClipboard::Selection);
}
clippedText = text;
ui->statusBar->showMessage(tr("Copied to clipboard"), 2000);
if (QtPassSettings::isUseAutoclear()) {
clearClipboardTimer.start();
}
}
void MainWindow::copyPasswordFromTreeview() {
QFileInfo fileOrFolder =
model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
if (fileOrFolder.isFile()) {
QString file = getFile(ui->treeView->currentIndex(), true);
connect(QtPassSettings::getPass(), &Pass::finishedShow, this,
&MainWindow::passwordFromFileToClipboard);
QtPassSettings::getPass()->Show(file);
}
}
void MainWindow::passwordFromFileToClipboard(const QString &text) {
QStringList tokens = text.split('\n');
copyTextToClipboard(tokens[0]);
}
/**
* @brief MainWindow::addToGridLayout add a field to the template grid
* @param position
* @param field
* @param value
*/
void MainWindow::addToGridLayout(int position, 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);
frame->setLayout(ly);
if (QtPassSettings::getClipBoardType() != Enums::CLIPBOARD_NEVER) {
QPushButtonWithClipboard *fieldLabel =
new QPushButtonWithClipboard(trimmedValue, this);
connect(fieldLabel, SIGNAL(clicked(QString)), this,
SLOT(copyTextToClipboard(QString)));
fieldLabel->setStyleSheet("border-style: none ; background: transparent;");
// fieldLabel->setContentsMargins(0,5,5,0);
frame->layout()->addWidget(fieldLabel);
}
// set the echo mode to password, if the field is "password"
if (QtPassSettings::isHidePassword() && trimmedField == tr("Password")) {
QLineEdit *line = new QLineEdit();
line->setObjectName(trimmedField);
line->setText(trimmedValue);
line->setReadOnly(true);
line->setStyleSheet("border-style: none ; background: transparent;");
line->setContentsMargins(0, 0, 0, 0);
line->setEchoMode(QLineEdit::Password);
frame->layout()->addWidget(line);
} else {
QTextBrowser *line = new QTextBrowser();
line->setOpenExternalLinks(true);
line->setOpenLinks(true);
line->setMaximumHeight(26);
line->setMinimumHeight(26);
line->setSizePolicy(
QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
line->setObjectName(trimmedField);
trimmedValue.replace(
QRegExp("((?:https?|ftp|ssh|sftp|ftps|webdav|webdavs)://\\S+)"),
"<a href=\"\\1\">\\1</a>");
line->setText(trimmedValue);
line->setReadOnly(true);
line->setStyleSheet("border-style: none ; background: transparent;");
line->setContentsMargins(0, 0, 0, 0);
frame->layout()->addWidget(line);
}
frame->setStyleSheet(
".QFrame{border: 1px solid lightgrey; border-radius: 5px;}");
// set into the layout
ui->gridLayout->addWidget(new QLabel(trimmedField), position, 0);
ui->gridLayout->addWidget(frame, position, 1);
}
/**
* @brief Displays message in status bar
*
* @param msg text to be displayed
* @param timeout time for which msg shall be visible
*/
void MainWindow::showStatusMessage(QString msg, int timeout) {
ui->statusBar->showMessage(msg, timeout);
}
/**
* @brief MainWindow::startReencryptPath disable ui elements and treeview
*/
void MainWindow::startReencryptPath() {
enableUiElements(false);
ui->treeView->setDisabled(true);
}
/**
* @brief MainWindow::endReencryptPath re-enable ui elements
*/
void MainWindow::endReencryptPath() { enableUiElements(true); }
/**
* @brief MainWindow::critical critical message popup wrapper.
* @param title
* @param msg
*/
void MainWindow::critical(QString title, QString msg) {
QMessageBox::critical(this, title, msg);
}
void MainWindow::updateGitButtonVisibility() {
if (!QtPassSettings::isUseGit() ||
(QtPassSettings::getGitExecutable().isEmpty() &&
QtPassSettings::getPassExecutable().isEmpty())) {
enableGitButtons(false);
} else {
enableGitButtons(true);
}
}
void MainWindow::updateOtpButtonVisibility() {
#if defined(Q_OS_WIN) || defined(__APPLE__)
ui->actionOtp->setVisible(false);
#endif
if (!QtPassSettings::isUseOtp())
ui->actionOtp->setEnabled(false);
else
ui->actionOtp->setEnabled(true);
}
void MainWindow::enableGitButtons(const bool &state) {
// Following GNOME guidelines is preferable disable buttons instead of hide
ui->actionPush->setEnabled(state);
ui->actionUpdate->setEnabled(state);
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jul 8, 12:27 PM (1 d, 3 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
65/c4/0a8efb28e69c4a6c0c44ded5d9d7

Event Timeline