QtPass 1.5.1
Multi-platform GUI for pass, the standard unix password manager.
Loading...
Searching...
No Matches
mainwindow.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2016 Anne Jan Brouwer
2// SPDX-License-Identifier: GPL-3.0-or-later
3#include "mainwindow.h"
4
5#ifdef QT_DEBUG
6#include "debughelper.h"
7#endif
8
9#include "configdialog.h"
10#include "filecontent.h"
11#include "passworddialog.h"
12#include "qpushbuttonasqrcode.h"
15#include "qtpass.h"
16#include "qtpasssettings.h"
17#include "trayicon.h"
18#include "ui_mainwindow.h"
19#include "usersdialog.h"
20#include "util.h"
21#include <QCloseEvent>
22#include <QDesktopServices>
23#include <QDialog>
24#include <QDirIterator>
25#include <QFileInfo>
26#include <QInputDialog>
27#include <QLabel>
28#include <QMenu>
29#include <QMessageBox>
30#include <QShortcut>
31#include <QTimer>
32#include <utility>
33
40MainWindow::MainWindow(const QString &searchText, QWidget *parent)
41 : QMainWindow(parent), ui(new Ui::MainWindow), keygen(nullptr),
42 tray(nullptr) {
43#ifdef __APPLE__
44 // extra treatment for mac os
45 // see http://doc.qt.io/qt-5/qkeysequence.html#qt_set_sequence_auto_mnemonic
46 qt_set_sequence_auto_mnemonic(true);
47#endif
48 ui->setupUi(this);
49
50 m_qtPass = new QtPass(this);
51
52 // register shortcut ctrl/cmd + Q to close the main window
53 new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q), this, SLOT(close()));
54 // register shortcut ctrl/cmd + C to copy the currently selected password
55 new QShortcut(QKeySequence(QKeySequence::StandardKey::Copy), this,
56 SLOT(copyPasswordFromTreeview()));
57
58 model.setNameFilters(QStringList() << "*.gpg");
59 model.setNameFilterDisables(false);
60
61 /*
62 * I added this to solve Windows bug but now on GNU/Linux the main folder,
63 * if hidden, disappear
64 *
65 * model.setFilter(QDir::NoDot);
66 */
67
69
70 QModelIndex rootDir = model.setRootPath(passStore);
71 model.fetchMore(rootDir);
72
73 proxyModel.setModelAndStore(&model, passStore);
74 selectionModel.reset(new QItemSelectionModel(&proxyModel));
75
76 ui->treeView->setModel(&proxyModel);
77 ui->treeView->setRootIndex(proxyModel.mapFromSource(rootDir));
78 ui->treeView->setColumnHidden(1, true);
79 ui->treeView->setColumnHidden(2, true);
80 ui->treeView->setColumnHidden(3, true);
81 ui->treeView->setHeaderHidden(true);
82 ui->treeView->setIndentation(15);
83 ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
84 ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
85 ui->treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
86 ui->treeView->sortByColumn(0, Qt::AscendingOrder);
87 connect(ui->treeView, &QWidget::customContextMenuRequested, this,
88 &MainWindow::showContextMenu);
89 connect(ui->treeView, &DeselectableTreeView::emptyClicked, this,
91
93 QFont monospace("Monospace");
94 monospace.setStyleHint(QFont::Monospace);
95 ui->textBrowser->setFont(monospace);
96 }
98 ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
99 }
100 ui->textBrowser->setOpenExternalLinks(true);
101 ui->textBrowser->setContextMenuPolicy(Qt::CustomContextMenu);
102 connect(ui->textBrowser, &QWidget::customContextMenuRequested, this,
103 &MainWindow::showBrowserContextMenu);
104
105 updateProfileBox();
106
107 QtPassSettings::getPass()->updateEnv();
108 clearPanelTimer.setInterval(MS_PER_SECOND *
110 clearPanelTimer.setSingleShot(true);
111 connect(&clearPanelTimer, &QTimer::timeout, this,
112 [this]() -> void { clearPanel(); });
113
114 searchTimer.setInterval(350);
115 searchTimer.setSingleShot(true);
116
117 connect(&searchTimer, &QTimer::timeout, this, &MainWindow::onTimeoutSearch);
118
119 initToolBarButtons();
120 initStatusBar();
121
122 ui->lineEdit->setClearButtonEnabled(true);
123
125
126 QTimer::singleShot(10, this, SLOT(focusInput()));
127
128 ui->lineEdit->setText(searchText);
129
130 if (!m_qtPass->init()) {
131 // no working config so this should just quit
132 QApplication::quit();
133 }
134}
135
136MainWindow::~MainWindow() { delete m_qtPass; }
137
144void MainWindow::focusInput() {
145 ui->lineEdit->selectAll();
146 ui->lineEdit->setFocus();
147}
148
153void MainWindow::changeEvent(QEvent *event) {
154 QWidget::changeEvent(event);
155 if (event->type() == QEvent::ActivationChange) {
156 if (isActiveWindow()) {
157 focusInput();
158 }
159 }
160}
161
165void MainWindow::initToolBarButtons() {
166 connect(ui->actionAddPassword, &QAction::triggered, this,
167 &MainWindow::addPassword);
168 connect(ui->actionAddFolder, &QAction::triggered, this,
169 &MainWindow::addFolder);
170 connect(ui->actionEdit, &QAction::triggered, this, &MainWindow::onEdit);
171 connect(ui->actionDelete, &QAction::triggered, this, &MainWindow::onDelete);
172 connect(ui->actionPush, &QAction::triggered, this, &MainWindow::onPush);
173 connect(ui->actionUpdate, &QAction::triggered, this, &MainWindow::onUpdate);
174 connect(ui->actionUsers, &QAction::triggered, this, &MainWindow::onUsers);
175 connect(ui->actionConfig, &QAction::triggered, this, &MainWindow::onConfig);
176 connect(ui->actionOtp, &QAction::triggered, this, &MainWindow::onOtp);
177
178 ui->actionAddPassword->setIcon(
179 QIcon::fromTheme("document-new", QIcon(":/icons/document-new.svg")));
180 ui->actionAddFolder->setIcon(
181 QIcon::fromTheme("folder-new", QIcon(":/icons/folder-new.svg")));
182 ui->actionEdit->setIcon(QIcon::fromTheme(
183 "document-properties", QIcon(":/icons/document-properties.svg")));
184 ui->actionDelete->setIcon(
185 QIcon::fromTheme("edit-delete", QIcon(":/icons/edit-delete.svg")));
186 ui->actionPush->setIcon(
187 QIcon::fromTheme("go-up", QIcon(":/icons/go-top.svg")));
188 ui->actionUpdate->setIcon(
189 QIcon::fromTheme("go-down", QIcon(":/icons/go-bottom.svg")));
190 ui->actionUsers->setIcon(QIcon::fromTheme(
191 "x-office-address-book", QIcon(":/icons/x-office-address-book.svg")));
192 ui->actionConfig->setIcon(QIcon::fromTheme(
193 "applications-system", QIcon(":/icons/applications-system.svg")));
194}
195
199void MainWindow::initStatusBar() {
200 ui->statusBar->showMessage(tr("Welcome to QtPass %1").arg(VERSION), 2000);
201
202 QPixmap logo = QPixmap::fromImage(QImage(":/artwork/icon.svg"))
203 .scaledToHeight(statusBar()->height());
204 auto *logoApp = new QLabel(statusBar());
205 logoApp->setPixmap(logo);
206 statusBar()->addPermanentWidget(logoApp);
207}
208
210 return ui->treeView->currentIndex();
211}
212
214 this->keygen->close();
215 this->keygen = nullptr;
216}
217
218void MainWindow::flashText(const QString &text, const bool isError,
219 const bool isHtml) {
220 if (isError) {
221 ui->textBrowser->setTextColor(Qt::red);
222 }
223
224 if (isHtml) {
225 QString _text = text;
226 if (!ui->textBrowser->toPlainText().isEmpty()) {
227 _text = ui->textBrowser->toHtml() + _text;
228 }
229 ui->textBrowser->setHtml(_text);
230 } else {
231 ui->textBrowser->setText(text);
232 ui->textBrowser->setTextColor(Qt::black);
233 }
234}
235
241 QScopedPointer<ConfigDialog> d(new ConfigDialog(this));
242 d->setModal(true);
243 // Automatically default to pass if it's available
244 if (m_qtPass->isFreshStart() &&
245 QFile(QtPassSettings::getPassExecutable()).exists()) {
247 }
248
249 if (m_qtPass->isFreshStart()) {
250 d->wizard(); // does shit
251 }
252 if (d->exec()) {
253 if (d->result() == QDialog::Accepted) {
254 // Update the textBrowser font
256 QFont monospace("Monospace");
257 monospace.setStyleHint(QFont::Monospace);
258 ui->textBrowser->setFont(monospace);
259 } else {
260 ui->textBrowser->setFont(QFont());
261 }
262 // Update the textBrowser line wrap mode
264 ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
265 } else {
266 ui->textBrowser->setLineWrapMode(QTextBrowser::WidgetWidth);
267 }
268
270 Qt::WindowFlags flags = windowFlags();
271 this->setWindowFlags(flags | Qt::WindowStaysOnTopHint);
272 } else {
273 this->setWindowFlags(Qt::Window);
274 }
275 this->show();
276
277 updateProfileBox();
278 ui->treeView->setRootIndex(proxyModel.mapFromSource(
279 model.setRootPath(QtPassSettings::getPassStore())));
280
281 if (m_qtPass->isFreshStart() && Util::checkConfig()) {
282 config();
283 }
284 QtPassSettings::getPass()->updateEnv();
285 clearPanelTimer.setInterval(MS_PER_SECOND *
287 m_qtPass->setClipboardTimer();
288
289 updateGitButtonVisibility();
290 updateOtpButtonVisibility();
291 if (QtPassSettings::isUseTrayIcon() && tray == nullptr) {
292 initTrayIcon();
293 } else if (!QtPassSettings::isUseTrayIcon() && tray != nullptr) {
294 destroyTrayIcon();
295 }
296 }
297
298 m_qtPass->setFreshStart(false);
299 }
300}
301
305void MainWindow::onUpdate(bool block) {
306 ui->statusBar->showMessage(tr("Updating password-store"), 2000);
307 if (block) {
308 QtPassSettings::getPass()->GitPull_b();
309 } else {
310 QtPassSettings::getPass()->GitPull();
311 }
312}
313
319 ui->statusBar->showMessage(tr("Updating password-store"), 2000);
320 QtPassSettings::getPass()->GitPush();
321 }
322}
323
331auto MainWindow::getFile(const QModelIndex &index, bool forPass) -> QString {
332 if (!index.isValid() ||
333 !model.fileInfo(proxyModel.mapToSource(index)).isFile()) {
334 return {};
335 }
336 QString filePath = model.filePath(proxyModel.mapToSource(index));
337 if (forPass) {
338 filePath = QDir(QtPassSettings::getPassStore()).relativeFilePath(filePath);
339 filePath.replace(Util::endsWithGpg(), "");
340 }
341 return filePath;
342}
343
348void MainWindow::on_treeView_clicked(const QModelIndex &index) {
349 bool cleared = ui->treeView->currentIndex().flags() == Qt::NoItemFlags;
350 currentDir =
351 Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
352 // TODO(bezet): "Could not decrypt";
353 m_qtPass->clearClippedText();
354 QString file = getFile(index, true);
355 ui->passwordName->setText(getFile(index, true));
356 if (!file.isEmpty() && !cleared) {
357 QtPassSettings::getPass()->Show(file);
358 } else {
359 clearPanel(false);
360 ui->actionEdit->setEnabled(false);
361 ui->actionDelete->setEnabled(true);
362 }
363}
364
370void MainWindow::on_treeView_doubleClicked(const QModelIndex &index) {
371 QFileInfo fileOrFolder =
372 model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
373
374 if (fileOrFolder.isFile()) {
375 editPassword(getFile(index, true));
376 }
377}
378
383 currentDir = "";
384 m_qtPass->clearClipboard();
385 ui->treeView->clearSelection();
386 ui->actionEdit->setEnabled(false);
387 ui->actionDelete->setEnabled(false);
388 ui->passwordName->setText("");
389 clearPanel(false);
390}
391
393 clearTemplateWidgets();
394 ui->textBrowser->clear();
396 clearPanelTimer.stop();
397}
398
399void MainWindow::passShowHandler(const QString &p_output) {
400 QStringList templ = QtPassSettings::isUseTemplate()
401 ? QtPassSettings::getPassTemplate().split("\n")
402 : QStringList();
403 bool allFields =
405 FileContent fileContent = FileContent::parse(p_output, templ, allFields);
406 QString output = p_output;
407 QString password = fileContent.getPassword();
408
409 // set clipped text
410 m_qtPass->setClippedText(password, p_output);
411
412 // first clear the current view:
413 clearTemplateWidgets();
414
415 // show what is needed:
417 output = "***" + tr("Content hidden") + "***";
418 } else if (!QtPassSettings::isDisplayAsIs()) {
419 if (!password.isEmpty()) {
420 // set the password, it is hidden if needed in addToGridLayout
421 addToGridLayout(0, tr("Password"), password);
422 }
423
424 NamedValues namedValues = fileContent.getNamedValues();
425 for (int j = 0; j < namedValues.length(); ++j) {
426 const NamedValue &nv = namedValues.at(j);
427 addToGridLayout(j + 1, nv.name, nv.value);
428 }
429 if (ui->gridLayout->count() == 0) {
430 ui->verticalLayoutPassword->setSpacing(0);
431 } else {
432 ui->verticalLayoutPassword->setSpacing(6);
433 }
434
435 output = fileContent.getRemainingDataForDisplay();
436 }
437
439 clearPanelTimer.start();
440 }
441
442 emit passShowHandlerFinished(output);
444}
445
446void MainWindow::passOtpHandler(const QString &p_output) {
447 if (!p_output.isEmpty()) {
448 addToGridLayout(ui->gridLayout->count() + 1, tr("OTP Code"), p_output);
449 m_qtPass->copyTextToClipboard(p_output);
450 showStatusMessage(tr("OTP code copied to clipboard"));
451 } else {
452 flashText(tr("No OTP code found in this password entry"), true);
453 }
455 clearPanelTimer.start();
456 }
458}
459
463void MainWindow::clearPanel(bool notify) {
464 while (ui->gridLayout->count() > 0) {
465 QLayoutItem *item = ui->gridLayout->takeAt(0);
466 delete item->widget();
467 delete item;
468 }
469 if (notify) {
470 QString output = "***" + tr("Password and Content hidden") + "***";
471 ui->textBrowser->setHtml(output);
472 } else {
473 ui->textBrowser->setHtml("");
474 }
475}
476
483 ui->treeView->setEnabled(state);
484 ui->lineEdit->setEnabled(state);
485 ui->lineEdit->installEventFilter(this);
486 ui->actionAddPassword->setEnabled(state);
487 ui->actionAddFolder->setEnabled(state);
488 ui->actionUsers->setEnabled(state);
489 ui->actionConfig->setEnabled(state);
490 // is a file selected?
491 state &= ui->treeView->currentIndex().isValid();
492 ui->actionDelete->setEnabled(state);
493 ui->actionEdit->setEnabled(state);
494 updateGitButtonVisibility();
495 updateOtpButtonVisibility();
496}
497
499 QByteArray geometry = QtPassSettings::getGeometry(saveGeometry());
500 restoreGeometry(geometry);
501 QByteArray savestate = QtPassSettings::getSavestate(saveState());
502 restoreState(savestate);
503 QPoint position = QtPassSettings::getPos(pos());
504 move(position);
505 QSize newSize = QtPassSettings::getSize(size());
506 resize(newSize);
507 if (QtPassSettings::isMaximized(isMaximized())) {
508 showMaximized();
509 }
510
512 Qt::WindowFlags flags = windowFlags();
513 setWindowFlags(flags | Qt::WindowStaysOnTopHint);
514 show();
515 }
516
517 if (QtPassSettings::isUseTrayIcon() && tray == nullptr) {
518 initTrayIcon();
520 // since we are still in constructor, can't directly hide
521 QTimer::singleShot(10, this, SLOT(hide()));
522 }
523 } else if (!QtPassSettings::isUseTrayIcon() && tray != nullptr) {
524 destroyTrayIcon();
525 }
526}
527
531void MainWindow::onConfig() { config(); }
532
538void MainWindow::on_lineEdit_textChanged(const QString &arg1) {
539 ui->statusBar->showMessage(tr("Looking for: %1").arg(arg1), 1000);
540 ui->treeView->expandAll();
541 clearPanel(false);
542 ui->passwordName->setText("");
543 ui->actionEdit->setEnabled(false);
544 ui->actionDelete->setEnabled(false);
545 searchTimer.start();
546}
547
552void MainWindow::onTimeoutSearch() {
553 QString query = ui->lineEdit->text();
554
555 if (query.isEmpty()) {
556 ui->treeView->collapseAll();
557 deselect();
558 }
559
560 query.replace(QStringLiteral(" "), ".*");
561 QRegularExpression regExp(query, QRegularExpression::CaseInsensitiveOption);
562 proxyModel.setFilterRegularExpression(regExp);
563 ui->treeView->setRootIndex(proxyModel.mapFromSource(
564 model.setRootPath(QtPassSettings::getPassStore())));
565
566 if (proxyModel.rowCount() > 0 && !query.isEmpty()) {
567 selectFirstFile();
568 } else {
569 ui->actionEdit->setEnabled(false);
570 ui->actionDelete->setEnabled(false);
571 }
572}
573
579void MainWindow::on_lineEdit_returnPressed() {
580#ifdef QT_DEBUG
581 dbg() << "on_lineEdit_returnPressed" << proxyModel.rowCount();
582#endif
583
584 if (proxyModel.rowCount() > 0) {
585 selectFirstFile();
586 on_treeView_clicked(ui->treeView->currentIndex());
587 }
588}
589
594void MainWindow::selectFirstFile() {
595 QModelIndex index = proxyModel.mapFromSource(
596 model.setRootPath(QtPassSettings::getPassStore()));
597 index = firstFile(index);
598 ui->treeView->setCurrentIndex(index);
599}
600
606auto MainWindow::firstFile(QModelIndex parentIndex) -> QModelIndex {
607 QModelIndex index = parentIndex;
608 int numRows = proxyModel.rowCount(parentIndex);
609 for (int row = 0; row < numRows; ++row) {
610 index = proxyModel.index(row, 0, parentIndex);
611 if (model.fileInfo(proxyModel.mapToSource(index)).isFile()) {
612 return index;
613 }
614 if (proxyModel.hasChildren(index)) {
615 return firstFile(index);
616 }
617 }
618 return index;
619}
620
626void MainWindow::setPassword(const QString &file, bool isNew) {
627 PasswordDialog d(file, isNew, this);
628
629 if (!d.exec()) {
630 ui->treeView->setFocus();
631 }
632}
633
638void MainWindow::addPassword() {
639 bool ok;
640 QString dir =
641 Util::getDir(ui->treeView->currentIndex(), true, model, proxyModel);
642 QString file =
643 QInputDialog::getText(this, tr("New file"),
644 tr("New password file: \n(Will be placed in %1 )")
646 Util::getDir(ui->treeView->currentIndex(),
647 true, model, proxyModel)),
648 QLineEdit::Normal, "", &ok);
649 if (!ok || file.isEmpty()) {
650 return;
651 }
652 file = dir + file;
653 setPassword(file);
654}
655
660void MainWindow::onDelete() {
661 QModelIndex currentIndex = ui->treeView->currentIndex();
662 if (!currentIndex.isValid()) {
663 // This fixes https://github.com/IJHack/QtPass/issues/556
664 // Otherwise the entire password directory would be deleted if
665 // nothing is selected in the tree view.
666 return;
667 }
668
669 QFileInfo fileOrFolder =
670 model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
671 QString file = "";
672 bool isDir = false;
673
674 if (fileOrFolder.isFile()) {
675 file = getFile(ui->treeView->currentIndex(), true);
676 } else {
677 file = Util::getDir(ui->treeView->currentIndex(), true, model, proxyModel);
678 isDir = true;
679 }
680
681 QString dirMessage = tr(" and the whole content?");
682 if (isDir) {
683 QDirIterator it(model.rootPath() + QDir::separator() + file,
684 QDirIterator::Subdirectories);
685 bool okDir = true;
686 while (it.hasNext() && okDir) {
687 it.next();
688 if (QFileInfo(it.filePath()).isFile()) {
689 if (QFileInfo(it.filePath()).suffix() != "gpg") {
690 okDir = false;
691 dirMessage = tr(" and the whole content? <br><strong>Attention: "
692 "there are unexpected files in the given folder, "
693 "check them before continue.</strong>");
694 }
695 }
696 }
697 }
698
699 if (QMessageBox::question(
700 this, isDir ? tr("Delete folder?") : tr("Delete password?"),
701 tr("Are you sure you want to delete %1%2?")
702 .arg(QDir::separator() + file, isDir ? dirMessage : "?"),
703 QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
704 return;
705 }
706
707 QtPassSettings::getPass()->Remove(file, isDir);
708}
709
713void MainWindow::onOtp() {
714 QString file = getFile(ui->treeView->currentIndex(), true);
715 if (!file.isEmpty()) {
718 QtPassSettings::getPass()->OtpGenerate(file);
719 }
720 } else {
721 flashText(tr("No password selected for OTP generation"), true);
722 }
723}
724
728void MainWindow::onEdit() {
729 QString file = getFile(ui->treeView->currentIndex(), true);
730 editPassword(file);
731}
732
737void MainWindow::userDialog(const QString &dir) {
738 if (!dir.isEmpty()) {
739 currentDir = dir;
740 }
741 onUsers();
742}
743
749void MainWindow::onUsers() {
750 QString dir =
751 currentDir.isEmpty()
752 ? Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel)
753 : currentDir;
754
755 UsersDialog d(dir, this);
756 if (!d.exec()) {
757 ui->treeView->setFocus();
758 }
759}
760
765void MainWindow::messageAvailable(const QString &message) {
766 if (message.isEmpty()) {
767 focusInput();
768 } else {
769 ui->treeView->expandAll();
770 ui->lineEdit->setText(message);
771 on_lineEdit_returnPressed();
772 }
773 show();
774 raise();
775}
776
782void MainWindow::generateKeyPair(const QString &batch, QDialog *keygenWindow) {
783 keygen = keygenWindow;
784 emit generateGPGKeyPair(batch);
785}
786
791void MainWindow::updateProfileBox() {
792 QHash<QString, QHash<QString, QString>> profiles =
794
795 if (profiles.isEmpty()) {
796 ui->profileWidget->hide();
797 } else {
798 ui->profileWidget->show();
799 ui->profileBox->setEnabled(profiles.size() > 1);
800 ui->profileBox->clear();
801 QHashIterator<QString, QHash<QString, QString>> i(profiles);
802 while (i.hasNext()) {
803 i.next();
804 if (!i.key().isEmpty()) {
805 ui->profileBox->addItem(i.key());
806 }
807 }
808 ui->profileBox->model()->sort(0);
809 }
810 int index = ui->profileBox->findText(QtPassSettings::getProfile());
811 if (index != -1) { // -1 for not found
812 ui->profileBox->setCurrentIndex(index);
813 }
814}
815
821#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
822void MainWindow::on_profileBox_currentIndexChanged(QString name) {
823#else
824void MainWindow::on_profileBox_currentTextChanged(const QString &name) {
825#endif
826 if (m_qtPass->isFreshStart() || name == QtPassSettings::getProfile()) {
827 return;
828 }
829
830 ui->lineEdit->clear();
831
833
835 QtPassSettings::getProfiles().value(name).value("path"));
837 QtPassSettings::getProfiles().value(name).value("signingKey"));
838 ui->statusBar->showMessage(tr("Profile changed to %1").arg(name), 2000);
839
840 QtPassSettings::getPass()->updateEnv();
841
842 ui->treeView->selectionModel()->clear();
843 ui->treeView->setRootIndex(proxyModel.mapFromSource(
844 model.setRootPath(QtPassSettings::getPassStore())));
845
846 ui->actionEdit->setEnabled(false);
847 ui->actionDelete->setEnabled(false);
848}
849
855void MainWindow::initTrayIcon() {
856 this->tray = new TrayIcon(this);
857 // Setup tray icon
858
859 if (tray == nullptr) {
860#ifdef QT_DEBUG
861 dbg() << "Allocating tray icon failed.";
862#endif
863 }
864
865 if (!tray->getIsAllocated()) {
866 destroyTrayIcon();
867 }
868}
869
873void MainWindow::destroyTrayIcon() {
874 delete this->tray;
875 tray = nullptr;
876}
877
882void MainWindow::closeEvent(QCloseEvent *event) {
884 this->hide();
885 event->ignore();
886 } else {
887 m_qtPass->clearClipboard();
888
889 QtPassSettings::setGeometry(saveGeometry());
890 QtPassSettings::setSavestate(saveState());
891 QtPassSettings::setMaximized(isMaximized());
892 if (!isMaximized()) {
895 }
896 event->accept();
897 }
898}
899
907auto MainWindow::eventFilter(QObject *obj, QEvent *event) -> bool {
908 if (obj == ui->lineEdit && event->type() == QEvent::KeyPress) {
909 auto *key = dynamic_cast<QKeyEvent *>(event);
910 if (key != nullptr && key->key() == Qt::Key_Down) {
911 ui->treeView->setFocus();
912 }
913 }
914 return QObject::eventFilter(obj, event);
915}
916
921void MainWindow::keyPressEvent(QKeyEvent *event) {
922 switch (event->key()) {
923 case Qt::Key_Delete:
924 onDelete();
925 break;
926 case Qt::Key_Return:
927 case Qt::Key_Enter:
928 if (proxyModel.rowCount() > 0) {
929 on_treeView_clicked(ui->treeView->currentIndex());
930 }
931 break;
932 case Qt::Key_Escape:
933 ui->lineEdit->clear();
934 break;
935 default:
936 break;
937 }
938}
939
945void MainWindow::showContextMenu(const QPoint &pos) {
946 QModelIndex index = ui->treeView->indexAt(pos);
947 bool selected = true;
948 if (!index.isValid()) {
949 ui->treeView->clearSelection();
950 ui->actionDelete->setEnabled(false);
951 ui->actionEdit->setEnabled(false);
952 currentDir = "";
953 selected = false;
954 }
955
956 ui->treeView->setCurrentIndex(index);
957
958 QPoint globalPos = ui->treeView->viewport()->mapToGlobal(pos);
959
960 QFileInfo fileOrFolder =
961 model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
962
963 QMenu contextMenu;
964 if (!selected || fileOrFolder.isDir()) {
965 QAction *openFolder =
966 contextMenu.addAction(tr("Open folder with file manager"));
967 QAction *addFolder = contextMenu.addAction(tr("Add folder"));
968 QAction *addPassword = contextMenu.addAction(tr("Add password"));
969 QAction *users = contextMenu.addAction(tr("Users"));
970 connect(openFolder, &QAction::triggered, this, &MainWindow::openFolder);
971 connect(addFolder, &QAction::triggered, this, &MainWindow::addFolder);
972 connect(addPassword, &QAction::triggered, this, &MainWindow::addPassword);
973 connect(users, &QAction::triggered, this, &MainWindow::onUsers);
974 } else if (fileOrFolder.isFile()) {
975 QAction *edit = contextMenu.addAction(tr("Edit"));
976 connect(edit, &QAction::triggered, this, &MainWindow::onEdit);
977 }
978 if (selected) {
979 // if (useClipboard != CLIPBOARD_NEVER) {
980 // contextMenu.addSeparator();
981 // QAction* copyItem = contextMenu.addAction(tr("Copy Password"));
982 // if (getClippedPassword().length() == 0) copyItem->setEnabled(false);
983 // connect(copyItem, SIGNAL(triggered()), this,
984 // SLOT(copyPasswordToClipboard()));
985 // }
986 contextMenu.addSeparator();
987 if (fileOrFolder.isDir()) {
988 QAction *renameFolder = contextMenu.addAction(tr("Rename folder"));
989 connect(renameFolder, &QAction::triggered, this,
990 &MainWindow::renameFolder);
991 } else if (fileOrFolder.isFile()) {
992 QAction *renamePassword = contextMenu.addAction(tr("Rename password"));
993 connect(renamePassword, &QAction::triggered, this,
994 &MainWindow::renamePassword);
995 }
996 QAction *deleteItem = contextMenu.addAction(tr("Delete"));
997 connect(deleteItem, &QAction::triggered, this, &MainWindow::onDelete);
998 }
999 contextMenu.exec(globalPos);
1000}
1001
1007void MainWindow::showBrowserContextMenu(const QPoint &pos) {
1008 QMenu *contextMenu = ui->textBrowser->createStandardContextMenu(pos);
1009 QPoint globalPos = ui->textBrowser->viewport()->mapToGlobal(pos);
1010
1011 contextMenu->exec(globalPos);
1012 delete contextMenu;
1013}
1014
1018void MainWindow::openFolder() {
1019 QString dir =
1020 Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
1021
1022 QString path = QDir::toNativeSeparators(dir);
1023 QDesktopServices::openUrl(QUrl::fromLocalFile(path));
1024}
1025
1029void MainWindow::addFolder() {
1030 bool ok;
1031 QString dir =
1032 Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel);
1033 QString newdir =
1034 QInputDialog::getText(this, tr("New file"),
1035 tr("New Folder: \n(Will be placed in %1 )")
1037 Util::getDir(ui->treeView->currentIndex(),
1038 true, model, proxyModel)),
1039 QLineEdit::Normal, "", &ok);
1040 if (!ok || newdir.isEmpty()) {
1041 return;
1042 }
1043 newdir.prepend(dir);
1044 // dbg()<< newdir;
1045 if (!QDir().mkdir(newdir)) {
1046 QMessageBox::warning(this, tr("Error"),
1047 tr("Failed to create folder: %1").arg(newdir));
1048 return;
1049 }
1050 if (QtPassSettings::isAddGPGId(true)) {
1051 QString gpgIdFile = newdir + "/.gpg-id";
1052 QFile gpgId(gpgIdFile);
1053 if (!gpgId.open(QIODevice::WriteOnly)) {
1054 QMessageBox::warning(
1055 this, tr("Error"),
1056 tr("Failed to create .gpg-id file in: %1").arg(newdir));
1057 return;
1058 }
1059 QList<UserInfo> users = QtPassSettings::getPass()->listKeys("", true);
1060 for (const UserInfo &user : users) {
1061 if (user.enabled) {
1062 gpgId.write((user.key_id + "\n").toUtf8());
1063 }
1064 }
1065 gpgId.close();
1066 }
1067}
1068
1072void MainWindow::renameFolder() {
1073 bool ok;
1074 QString srcDir = QDir::cleanPath(
1075 Util::getDir(ui->treeView->currentIndex(), false, model, proxyModel));
1076 QString srcDirName = QDir(srcDir).dirName();
1077 QString newName =
1078 QInputDialog::getText(this, tr("Rename file"), tr("Rename Folder To: "),
1079 QLineEdit::Normal, srcDirName, &ok);
1080 if (!ok || newName.isEmpty()) {
1081 return;
1082 }
1083 QString destDir = srcDir;
1084 destDir.replace(srcDir.lastIndexOf(srcDirName), srcDirName.length(), newName);
1085 QtPassSettings::getPass()->Move(srcDir, destDir);
1086}
1087
1092void MainWindow::editPassword(const QString &file) {
1093 if (!file.isEmpty()) {
1095 onUpdate(true);
1096 }
1097 setPassword(file, false);
1098 }
1099}
1100
1104void MainWindow::renamePassword() {
1105 bool ok;
1106 QString file = getFile(ui->treeView->currentIndex(), false);
1107 QString filePath = QFileInfo(file).path();
1108 QString fileName = QFileInfo(file).fileName();
1109 if (fileName.endsWith(".gpg", Qt::CaseInsensitive)) {
1110 fileName.chop(4);
1111 }
1112
1113 QString newName =
1114 QInputDialog::getText(this, tr("Rename file"), tr("Rename File To: "),
1115 QLineEdit::Normal, fileName, &ok);
1116 if (!ok || newName.isEmpty()) {
1117 return;
1118 }
1119 QString newFile = QDir(filePath).filePath(newName);
1120 QtPassSettings::getPass()->Move(file, newFile);
1121}
1122
1127void MainWindow::clearTemplateWidgets() {
1128 while (ui->gridLayout->count() > 0) {
1129 QLayoutItem *item = ui->gridLayout->takeAt(0);
1130 delete item->widget();
1131 delete item;
1132 }
1133 ui->verticalLayoutPassword->setSpacing(0);
1134}
1135
1136void MainWindow::copyPasswordFromTreeview() {
1137 QFileInfo fileOrFolder =
1138 model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
1139
1140 if (fileOrFolder.isFile()) {
1141 QString file = getFile(ui->treeView->currentIndex(), true);
1143 &MainWindow::passwordFromFileToClipboard);
1144 QtPassSettings::getPass()->Show(file);
1145 }
1146}
1147
1148void MainWindow::passwordFromFileToClipboard(const QString &text) {
1149 QStringList tokens = text.split('\n');
1150 m_qtPass->copyTextToClipboard(tokens[0]);
1151}
1152
1159void MainWindow::addToGridLayout(int position, const QString &field,
1160 const QString &value) {
1161 QString trimmedField = field.trimmed();
1162 QString trimmedValue = value.trimmed();
1163
1164 // Combine the Copy button and the line edit in one widget
1165 auto *frame = new QFrame();
1166 QLayout *ly = new QHBoxLayout();
1167 ly->setContentsMargins(5, 2, 2, 2);
1168 ly->setSpacing(0);
1169 frame->setLayout(ly);
1171 auto *fieldLabel = new QPushButtonWithClipboard(trimmedValue, this);
1172 connect(fieldLabel, &QPushButtonWithClipboard::clicked, m_qtPass,
1174
1175 fieldLabel->setStyleSheet(
1176 "border-style: none ; background: transparent; padding: 0; margin: 0;");
1177 frame->layout()->addWidget(fieldLabel);
1178 }
1179
1181 auto *qrbutton = new QPushButtonAsQRCode(trimmedValue, this);
1182 connect(qrbutton, &QPushButtonAsQRCode::clicked, m_qtPass,
1184 qrbutton->setStyleSheet(
1185 "border-style: none ; background: transparent; padding: 0; margin: 0;");
1186 frame->layout()->addWidget(qrbutton);
1187 }
1188
1189 // set the echo mode to password, if the field is "password"
1190 const QString lineStyle =
1192 ? "border-style: none; background: transparent; font-family: "
1193 "monospace;"
1194 : "border-style: none; background: transparent;";
1195
1196 if (QtPassSettings::isHidePassword() && trimmedField == tr("Password")) {
1197 auto *line = new QLineEdit();
1198 line->setObjectName(trimmedField);
1199 line->setText(trimmedValue);
1200 line->setReadOnly(true);
1201 line->setStyleSheet(lineStyle);
1202 line->setContentsMargins(0, 0, 0, 0);
1203 line->setEchoMode(QLineEdit::Password);
1204 auto *showButton = new QPushButtonShowPassword(line, this);
1205 showButton->setStyleSheet(
1206 "border-style: none ; background: transparent; padding: 0; margin: 0;");
1207 showButton->setContentsMargins(0, 0, 0, 0);
1208 frame->layout()->addWidget(showButton);
1209 frame->layout()->addWidget(line);
1210 } else {
1211 auto *line = new QTextBrowser();
1212 line->setOpenExternalLinks(true);
1213 line->setOpenLinks(true);
1214 line->setMaximumHeight(26);
1215 line->setMinimumHeight(26);
1216 line->setSizePolicy(
1217 QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
1218 line->setObjectName(trimmedField);
1219 trimmedValue.replace(Util::protocolRegex(), R"(<a href="\1">\1</a>)");
1220 line->setText(trimmedValue);
1221 line->setReadOnly(true);
1222 line->setStyleSheet(lineStyle);
1223 line->setContentsMargins(0, 0, 0, 0);
1224 frame->layout()->addWidget(line);
1225 }
1226
1227 frame->setStyleSheet(
1228 ".QFrame{border: 1px solid lightgrey; border-radius: 5px;}");
1229
1230 // set into the layout
1231 ui->gridLayout->addWidget(new QLabel(trimmedField), position, 0);
1232 ui->gridLayout->addWidget(frame, position, 1);
1233}
1234
1241void MainWindow::showStatusMessage(const QString &msg, int timeout) {
1242 ui->statusBar->showMessage(msg, timeout);
1243}
1244
1249 setUiElementsEnabled(false);
1250 ui->treeView->setDisabled(true);
1251}
1252
1257
1258void MainWindow::updateGitButtonVisibility() {
1259 if (!QtPassSettings::isUseGit() ||
1260 (QtPassSettings::getGitExecutable().isEmpty() &&
1261 QtPassSettings::getPassExecutable().isEmpty())) {
1262 enableGitButtons(false);
1263 } else {
1264 enableGitButtons(true);
1265 }
1266}
1267
1268void MainWindow::updateOtpButtonVisibility() {
1269#if defined(Q_OS_WIN) || defined(__APPLE__)
1270 ui->actionOtp->setVisible(false);
1271#endif
1272 if (!QtPassSettings::isUseOtp()) {
1273 ui->actionOtp->setEnabled(false);
1274 } else {
1275 ui->actionOtp->setEnabled(true);
1276 }
1277}
1278
1279void MainWindow::enableGitButtons(const bool &state) {
1280 // Following GNOME guidelines is preferable disable buttons instead of hide
1281 ui->actionPush->setEnabled(state);
1282 ui->actionUpdate->setEnabled(state);
1283}
1284
1290void MainWindow::critical(const QString &title, const QString &msg) {
1291 QMessageBox::critical(this, title, msg);
1292}
The ConfigDialog handles the configuration interface.
void emptyClicked()
emptyClicked event
auto getNamedValues() const -> NamedValues
auto getRemainingDataForDisplay() const -> QString
auto getPassword() const -> QString
static auto parse(const QString &fileContent, const QStringList &templateFields, bool allFields) -> FileContent
parse parses the given fileContent in a FileContent object. The password is accessible through getPas...
The MainWindow class does way too much, not only is it a switchboard, configuration handler and more,...
Definition mainwindow.h:39
void startReencryptPath()
MainWindow::startReencryptPath disable ui elements and treeview.
void passShowHandler(const QString &)
void endReencryptPath()
MainWindow::endReencryptPath re-enable ui elements.
void executeWrapperStarted()
void generateKeyPair(const QString &, QDialog *)
MainWindow::generateKeyPair internal gpg keypair generator . .
void closeEvent(QCloseEvent *event)
MainWindow::closeEvent hide or quit.
void critical(const QString &, const QString &)
MainWindow::critical critical message popup wrapper.
void messageAvailable(const QString &message)
MainWindow::messageAvailable we have some text/message/search to do.
MainWindow(const QString &searchText=QString(), QWidget *parent=nullptr)
MainWindow::MainWindow handles all of the main functionality and also the main window.
void onPush()
MainWindow::onPush do a git push.
void showStatusMessage(const QString &msg, int timeout=2000)
Displays message in status bar.
void passOtpHandler(const QString &)
void passShowHandlerFinished(const QString &output)
void generateGPGKeyPair(const QString &batch)
void restoreWindow()
void keyPressEvent(QKeyEvent *event)
MainWindow::keyPressEvent did anyone press return, enter or escape?
void flashText(const QString &text, const bool isError, const bool isHtml=false)
void userDialog(const QString &="")
MainWindow::userDialog see MainWindow::onUsers()
void setUiElementsEnabled(bool state)
MainWindow::setUiElementsEnabled enable or disable the relevant UI elements.
auto eventFilter(QObject *obj, QEvent *event) -> bool
MainWindow::eventFilter filter out some events and focus the treeview.
void changeEvent(QEvent *event)
MainWindow::changeEvent sets focus to the search box.
auto getCurrentTreeViewIndex() -> QModelIndex
void cleanKeygenDialog()
void deselect()
MainWindow::deselect clear the selection, password and copy buffer.
void on_treeView_clicked(const QModelIndex &index)
MainWindow::on_treeView_clicked read the selected password file.
void config()
MainWindow::config pops up the configuration screen and handles all inter-window communication.
The NamedValues class is mostly a list of NamedValue but also has a method to take a specific NamedVa...
Definition filecontent.h:19
void finishedShow(const QString &)
PasswordDialog Handles the inserting and editing of passwords.
Stylish widget to display the field as QR Code.
void clicked(const QString &)
Stylish widget to allow copying of password and account details.
void clicked(const QString &)
void clearClippedText()
Definition qtpass.cpp:376
void setClipboardTimer()
Definition qtpass.cpp:378
auto isFreshStart() -> bool
Definition qtpass.h:23
void clearClipboard()
MainWindow::clearClipboard remove clipboard contents.
Definition qtpass.cpp:386
void setFreshStart(const bool &fs)
Definition qtpass.h:24
void setClippedText(const QString &, const QString &p_output=QString())
Definition qtpass.cpp:367
void copyTextToClipboard(const QString &text)
MainWindow::copyTextToClipboard copies text to your clipboard.
Definition qtpass.cpp:411
auto init() -> bool
QtPass::init make sure we are ready to go as soon as possible.
Definition qtpass.cpp:70
void showTextAsQRCode(const QString &text)
displays the text as qrcode
Definition qtpass.cpp:430
static void setMaximized(const bool &maximized)
static auto isStartMinimized(const bool &defaultValue=QVariant().toBool()) -> bool
static auto isUseOtp(const bool &defaultValue=QVariant().toBool()) -> bool
static void setProfile(const QString &profile)
static auto isNoLineWrapping(const bool &defaultValue=QVariant().toBool()) -> bool
static void setPassStore(const QString &passStore)
static auto isHideContent(const bool &defaultValue=QVariant().toBool()) -> bool
static auto getSize(const QSize &defaultValue=QVariant().toSize()) -> QSize
static auto getPass() -> Pass *
static auto isUseQrencode(const bool &defaultValue=QVariant().toBool()) -> bool
static auto isUseAutoclearPanel(const bool &defaultValue=QVariant().toBool()) -> bool
static auto isAutoPull(const bool &defaultValue=QVariant().toBool()) -> bool
static auto isUseGit(const bool &defaultValue=QVariant().toBool()) -> bool
static auto isTemplateAllFields(const bool &defaultValue=QVariant().toBool()) -> bool
static void setPassSigningKey(const QString &passSigningKey)
static auto getPassStore(const QString &defaultValue=QVariant().toString()) -> QString
static auto isUseTemplate(const bool &defaultValue=QVariant().toBool()) -> bool
static auto isUseTrayIcon(const bool &defaultValue=QVariant().toBool()) -> bool
static auto isAddGPGId(const bool &defaultValue=QVariant().toBool()) -> bool
static void setPos(const QPoint &pos)
static auto getPassTemplate(const QString &defaultValue=QVariant().toString()) -> QString
static auto getClipBoardType(const Enums::clipBoardType &defaultvalue=Enums::CLIPBOARD_NEVER) -> Enums::clipBoardType
static auto isMaximized(const bool &defaultValue=QVariant().toBool()) -> bool
static auto getProfile(const QString &defaultValue=QVariant().toString()) -> QString
static auto isUseMonospace(const bool &defaultValue=QVariant().toBool()) -> bool
static void setUsePass(const bool &usePass)
static auto getAutoclearPanelSeconds(const int &defaultValue=QVariant().toInt()) -> int
static void setSavestate(const QByteArray &saveState)
static auto isAlwaysOnTop(const bool &defaultValue=QVariant().toBool()) -> bool
static auto isHidePassword(const bool &defaultValue=QVariant().toBool()) -> bool
static auto getPassExecutable(const QString &defaultValue=QVariant().toString()) -> QString
static auto getPos(const QPoint &defaultValue=QVariant().toPoint()) -> QPoint
static auto isHideOnClose(const bool &defaultValue=QVariant().toBool()) -> bool
static auto getProfiles() -> QHash< QString, QHash< QString, QString > >
static void setSize(const QSize &size)
static void setGeometry(const QByteArray &geometry)
static auto getGitExecutable(const QString &defaultValue=QVariant().toString()) -> QString
static auto getGeometry(const QByteArray &defaultValue=QVariant().toByteArray()) -> QByteArray
static auto isDisplayAsIs(const bool &defaultValue=QVariant().toBool()) -> bool
static auto getSavestate(const QByteArray &defaultValue=QVariant().toByteArray()) -> QByteArray
void setModelAndStore(QFileSystemModel *sourceModel, QString passStore)
StoreModel::setModelAndStore update the source model and store.
Handles the systemtray icon and menu.
Definition trayicon.h:16
auto getIsAllocated() -> bool
TrayIcon::getIsAllocated return if TrayIcon is allocated.
Definition trayicon.cpp:61
Handles listing and editing of GPG users.
Definition usersdialog.h:25
static auto protocolRegex() -> const QRegularExpression &
Definition util.cpp:208
static auto endsWithGpg() -> const QRegularExpression &
Definition util.cpp:203
static auto findPasswordStore() -> QString
Util::findPasswordStore look for common .password-store folder location.
Definition util.cpp:58
static auto getDir(const QModelIndex &index, bool forPass, const QFileSystemModel &model, const StoreModel &storeModel) -> QString
Util::getDir get selectd folder path.
Definition util.cpp:164
static auto checkConfig() -> bool
Util::checkConfig do we have prequisite settings?
Definition util.cpp:146
#define dbg()
Definition debughelper.h:9
@ CLIPBOARD_NEVER
Definition enums.h:13
QString name
Definition filecontent.h:11
QString value
Definition filecontent.h:12
Stores key info lines including validity, creation date and more.
Definition userinfo.h:13
constexpr int MS_PER_SECOND
Definition util.h:12