QtPass 2.0.0
Developer documentation
Loading...
Searching...
No Matches
firstrunwizard.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
2// SPDX-License-Identifier: GPL-3.0-or-later
8#include "firstrunwizard.h"
9#include "executor.h"
10#include "gpgkeystate.h"
11#include "imitatepass.h"
12#include "keygendialog.h"
13#include "passbackendfactory.h"
14#include "profileinit.h"
15#include "qtpasssettings.h"
16#include "util.h"
17#include <QCheckBox>
18#include <QDateTime>
19#include <QDir>
20#include <QFile>
21#include <QFileDialog>
22#include <QFileInfo>
23#include <QFormLayout>
24#include <QHBoxLayout>
25#include <QLabel>
26#include <QLineEdit>
27#include <QListWidget>
28#include <QMessageBox>
29#include <QPushButton>
30#include <QSystemTrayIcon>
31#include <QToolButton>
32#include <QVBoxLayout>
33#include <functional>
34#ifdef Q_OS_WIN
35#include <windows.h>
36#endif
37
38namespace {
39
41auto pathRow(QLineEdit *edit, QWidget *parent,
42 const std::function<QString()> &pick) -> QWidget * {
43 auto *row = new QWidget(parent);
44 auto *layout = new QHBoxLayout(row);
45 layout->setContentsMargins(0, 0, 0, 0);
46 layout->addWidget(edit);
47 auto *browse = new QToolButton(row);
48 browse->setText(QStringLiteral("…"));
49 browse->setToolTip(FirstRunWizard::tr("Browse"));
50 layout->addWidget(browse);
51 QObject::connect(browse, &QToolButton::clicked, edit, [edit, pick]() {
52 const QString chosen = pick();
53 if (!chosen.isEmpty()) {
54 edit->setText(chosen);
55 emit edit->textEdited(chosen);
56 }
57 });
58 return row;
59}
60
61auto runnable(const QString &exe) -> bool {
62 return Executor::parseWslCommand(exe).has_value() ||
63 (!exe.isEmpty() && QFileInfo(exe).isExecutable());
64}
65
66} // namespace
67
68// --------------------------------------------------------------------------
69
71 : QWizard(parent), m_settings(QtPassSettings::load()) {
72 setWindowTitle(tr("Welcome to QtPass"));
73 setWizardStyle(QWizard::ModernStyle);
74 setOption(QWizard::NoBackButtonOnStartPage);
75 setOption(QWizard::HaveHelpButton, false);
76
77 auto *intro = new QWizardPage(this);
78 intro->setTitle(tr("Welcome to QtPass"));
79 auto *introLayout = new QVBoxLayout(intro);
80 auto *introText = new QLabel(
81 tr("QtPass is a graphical front-end for <i>pass</i>, the standard Unix "
82 "password manager: every password is a file encrypted with GnuPG, "
83 "kept in a folder you can put under Git.<br><br>The next pages find "
84 "GnuPG, make sure you have a key to encrypt to and pick the folder "
85 "your passwords live in. Nothing is written until you press "
86 "Finish."),
87 intro);
88 introText->setWordWrap(true);
89 introText->setTextFormat(Qt::RichText);
90 introLayout->addWidget(introText);
91 setPage(IntroPage, intro);
92 setPage(ProgramsPage, new ProgramsWizardPage(this));
93 setPage(KeyPage, new KeyWizardPage(this));
94 setPage(StorePage, new StoreWizardPage(this));
95 setPage(DonePage, new DoneWizardPage(this));
96 setStartId(IntroPage);
97 resize(640, 480);
98}
99
100auto FirstRunWizard::secretKeys(const QString &gpgExecutable)
101 -> QList<UserInfo> {
102 if (!runnable(gpgExecutable)) {
103 return {};
104 }
105 QString out;
106 const int rc = Executor::executeBlocking(
107 gpgExecutable,
108 {QStringLiteral("--no-tty"), QStringLiteral("--with-colons"),
109 QStringLiteral("--with-fingerprint"),
110 QStringLiteral("--list-secret-keys")},
111 &out);
112 if (rc != 0) {
113 return {};
114 }
115 return parseGpgColonOutput(out, true);
116}
117
118auto FirstRunWizard::isStore(const QString &path) -> bool {
119 return !path.isEmpty() &&
120 QFile::exists(QDir(path).filePath(QStringLiteral(".gpg-id")));
121}
122
123auto FirstRunWizard::initialiseStore(const QString &store) -> bool {
124 QList<UserInfo> recipients;
125 for (const UserInfo &key : std::as_const(m_keys)) {
126 if (key.enabled) {
127 recipients.append(key);
128 }
129 }
130 const bool created = !QDir(store).exists();
131 if (created && !Util::mkpathForStore(store)) {
132 QMessageBox::warning(
133 this, tr("Error"),
134 tr("Failed to create password-store at: %1").arg(store));
135 return false;
136 }
137#ifdef Q_OS_WIN
138 // Only a folder made here is hidden, like ~/.password-store on Unix; a
139 // folder the user picked keeps its attributes.
140 if (created) {
141 SetFileAttributes(store.toStdWString().c_str(), FILE_ATTRIBUTE_HIDDEN);
142 }
143#endif
144 QString note;
145 if (!ProfileInit::initialise(store, recipients, m_settings, m_settings.useGit,
146 &note)) {
147 // ProfileInit writes .gpg-id before it signs and commits; take it back so
148 // a second Finish runs the whole initialisation again instead of finding
149 // a store that looks finished.
150 QFile::remove(QDir(store).filePath(QStringLiteral(".gpg-id")));
151 QFile::remove(QDir(store).filePath(QStringLiteral(".gpg-id.sig")));
152 QMessageBox::warning(this, tr("Password store not initialised"), note);
153 return false;
154 }
155 if (!note.isEmpty()) {
156 // ProfileInit's own note talks of switching profiles; on the first run
157 // the folder simply becomes the store.
158 QMessageBox::information(
159 this, tr("Password store"),
160 tr("%1 already contains encrypted files; they were not "
161 "re-encrypted to the ticked keys. Open Users after the start "
162 "to do that.")
163 .arg(QDir::toNativeSeparators(store)));
164 }
165 return true;
166}
167
168auto FirstRunWizard::initialiseGit(const QString &store) -> bool {
169 QString note;
170 if (ProfileInit::initGit(store, m_settings, &note)) {
171 return true;
172 }
173 // Take the repository back too, so a second Finish retries the whole thing
174 // instead of skipping a .git that never got its first commit.
175 QDir(QDir(store).filePath(QStringLiteral(".git"))).removeRecursively();
176 QMessageBox::warning(this, tr("Password store not initialised"), note);
177 return false;
178}
179
181 // The Finish button validates the page before calling this; a programmatic
182 // accept() gets the same treatment so the last page's choices are in.
183 if (currentPage() != nullptr && !currentPage()->validatePage()) {
184 return;
185 }
186 const QString store = QDir::cleanPath(m_settings.passStore);
187 const bool needsGit =
188 m_settings.useGit && !QDir(store).exists(QStringLiteral(".git"));
189 const bool ready = !isStore(store) ? initialiseStore(store)
190 : needsGit ? initialiseGit(store)
191 : true;
192 if (!ready) {
193 return;
194 }
197 // The backend cached the old paths at construction.
199 QWizard::accept();
200}
201
202// --------------------------------------------------------------------------
203
205 : QWizardPage(wizard), m_wizard(wizard), m_gpg(new QLineEdit(this)),
206 m_git(new QLineEdit(this)), m_pass(new QLineEdit(this)),
207 m_usePass(new QCheckBox(tr("Use the pass command-line tool"), this)),
208 m_gpgStatus(new QLabel(this)) {
209 setTitle(tr("Programs"));
210 setSubTitle(tr("GnuPG does the encrypting. pass and Git are optional; "
211 "QtPass can do their work itself."));
212 auto *layout = new QFormLayout(this);
213 auto pickExe = [this]() {
214 return QFileDialog::getOpenFileName(this, tr("Select executable"),
215 QString(), tr("All files (*)"));
216 };
217 layout->addRow(tr("GnuPG"), pathRow(m_gpg, this, pickExe));
218 m_gpgStatus->setWordWrap(true);
219 layout->addRow(QString(), m_gpgStatus);
220 layout->addRow(tr("Git"), pathRow(m_git, this, pickExe));
221 layout->addRow(tr("pass"), pathRow(m_pass, this, pickExe));
222 m_usePass->setToolTip(tr("Run the pass script for every operation instead "
223 "of calling gpg and git directly"));
224 layout->addRow(QString(), m_usePass);
225 for (QLineEdit *edit : {m_gpg, m_git, m_pass}) {
226 connect(edit, &QLineEdit::textEdited, this,
228 }
229}
230
233 // QtPassSettings::initExecutables() fills empty paths from PATH only; a
234 // stored path whose binary has moved would be shown as an error here, so
235 // look it up again in that case.
236 auto detect = [](QString &stored, const QStringList &names) {
237 if (runnable(stored)) {
238 return;
239 }
240 for (const QString &name : names) {
241 const QString found = Util::findBinaryInPath(name);
242 if (!found.isEmpty()) {
243 stored = found;
244 return;
245 }
246 }
247 };
248 detect(s.gpgExecutable, {QStringLiteral("gpg2"), QStringLiteral("gpg")});
249 detect(s.gitExecutable, {QStringLiteral("git")});
250 detect(s.passExecutable, {QStringLiteral("pass")});
251 detect(s.pwgenExecutable, {QStringLiteral("pwgen")});
252 m_gpg->setText(s.gpgExecutable);
253 m_git->setText(s.gitExecutable);
254 m_pass->setText(s.passExecutable);
255 m_usePass->setChecked(s.usePass || runnable(s.passExecutable));
256 updateStatus();
257}
258
260 const QString gpg = m_gpg->text().trimmed();
261 if (gpg.isEmpty()) {
262 m_gpgStatus->setText(tr("GnuPG was not found. Install it (gpg or gpg2) "
263 "and enter its location here."));
264 } else if (!runnable(gpg)) {
265 m_gpgStatus->setText(tr("%1 is not an executable file.").arg(gpg));
266 } else {
267 m_gpgStatus->setText(QString());
268 }
269 m_usePass->setEnabled(runnable(m_pass->text().trimmed()));
270 if (!m_usePass->isEnabled()) {
271 m_usePass->setChecked(false);
272 }
273 emit completeChanged();
274}
275
276auto ProgramsWizardPage::isComplete() const -> bool {
277 return runnable(m_gpg->text().trimmed());
278}
279
281 AppSettings &s = m_wizard->m_settings;
282 s.gpgExecutable = m_gpg->text().trimmed();
283 s.gitExecutable = m_git->text().trimmed();
284 s.passExecutable = m_pass->text().trimmed();
285 s.usePass = m_usePass->isChecked();
286 return true;
287}
288
289// --------------------------------------------------------------------------
290
292 : QWizardPage(wizard), m_wizard(wizard), m_list(new QListWidget(this)),
293 m_generate(new QPushButton(tr("Generate a new key pair…"), this)),
294 m_status(new QLabel(this)) {
295 setTitle(tr("Your key"));
296 setSubTitle(tr("Passwords are encrypted to GnuPG keys. Tick the keys that "
297 "should be able to open a new store; you need at least one "
298 "with its secret half on this machine."));
299 auto *layout = new QVBoxLayout(this);
300 layout->addWidget(m_list);
301 m_status->setWordWrap(true);
302 layout->addWidget(m_status);
303 auto *buttons = new QHBoxLayout;
304 buttons->addStretch();
305 buttons->addWidget(m_generate);
306 layout->addLayout(buttons);
307 connect(m_generate, &QPushButton::clicked, this, &KeyWizardPage::generate);
308 connect(m_list, &QListWidget::itemChanged, this,
309 [this]() { emit completeChanged(); });
310}
311
313
317 m_list->clear();
318 const QDateTime now = QDateTime::currentDateTime();
319 for (UserInfo &key : m_wizard->m_keys) {
320 // An expired or otherwise unusable key would make every encryption to
321 // the new store fail; list it, but do not tick it.
322 QString why;
323 if (key.expiry.isValid() && key.expiry < now) {
324 why = tr("expired");
325 } else if (!key.isValid()) {
326 why = tr("not usable");
327 }
328 key.enabled = why.isEmpty();
329 QString label = QStringLiteral("%1\n%2").arg(key.name, key.key_id);
330 if (!why.isEmpty()) {
331 label += QStringLiteral(" (%1)").arg(why);
332 }
333 auto *item = new QListWidgetItem(label, m_list);
334 item->setFlags(key.enabled ? (item->flags() | Qt::ItemIsUserCheckable)
335 : (item->flags() & ~Qt::ItemIsEnabled));
336 item->setCheckState(key.enabled ? Qt::Checked : Qt::Unchecked);
337 }
338 m_status->setText(m_wizard->m_keys.isEmpty()
339 ? tr("GnuPG has no secret key yet. Generate one here, "
340 "or import your existing key with gpg first.")
341 : QString());
342 emit completeChanged();
343}
344
346 // A backend of the wizard's own, set up from its copy of the settings, so
347 // the chosen gpg is used and nothing is written before Finish. Each Pass
348 // owns its Executor; the shared backend is not involved.
349 ImitatePass pass;
350 pass.init(m_wizard->m_settings);
352 d.exec();
353 reload();
354}
355
356auto KeyWizardPage::isComplete() const -> bool { return true; }
357
359 for (int i = 0; i < m_list->count() && i < m_wizard->m_keys.size(); ++i) {
360 m_wizard->m_keys[i].enabled = m_list->item(i)->checkState() == Qt::Checked;
361 }
362 return true;
363}
364
365// --------------------------------------------------------------------------
366
368 : QWizardPage(wizard), m_wizard(wizard), m_path(new QLineEdit(this)),
369 m_useGit(new QCheckBox(tr("Keep the store under Git"), this)),
370 m_status(new QLabel(this)) {
371 setTitle(tr("Password store"));
372 setSubTitle(tr("The folder your passwords live in. An existing store is "
373 "used as it is; an empty or missing folder is set up for "
374 "the keys you ticked."));
375 auto *layout = new QFormLayout(this);
376 layout->addRow(tr("Folder"), pathRow(m_path, this, [this]() {
377 return QFileDialog::getExistingDirectory(
378 this, tr("Choose the password store folder"),
379 m_path->text());
380 }));
381 m_status->setWordWrap(true);
382 layout->addRow(QString(), m_status);
383 layout->addRow(QString(), m_useGit);
384 connect(m_path, &QLineEdit::textEdited, this, &StoreWizardPage::updateStatus);
385}
386
388 QString path = m_wizard->m_settings.passStore;
389 if (path.isEmpty()) {
391 }
392 m_path->setText(QDir::toNativeSeparators(QDir::cleanPath(path)));
393 const QString clean = QDir::cleanPath(path);
394 const bool repository = QDir(clean).exists(QStringLiteral(".git"));
395 bool haveGit = runnable(m_wizard->m_settings.gitExecutable);
396 // A first commit needs a name and an e-mail; without them Finish would
397 // fail at the very end. Ask git now and say what to do instead.
398 const bool identity =
399 haveGit && (repository || ProfileInit::gitIdentityConfigured(
400 QDir::homePath(), m_wizard->m_settings));
401 m_useGit->setToolTip(
402 haveGit && !identity
403 ? tr("Git has no name and e-mail to commit with yet. Run\n"
404 "git config --global user.name \"Your Name\"\n"
405 "git config --global user.email you@example.org\n"
406 "and turn Git on in Settings afterwards.")
407 : tr("Every change becomes a commit; a folder that is no "
408 "repository yet gets one"));
409 haveGit = haveGit && identity;
410 m_useGit->setEnabled(haveGit);
411 // On for a repository or a store still to be made, off for an existing
412 // store that is no repository: turning Git on there would make every
413 // remove fail in a folder git knows nothing about.
414 m_useGit->setChecked(haveGit && (m_wizard->m_settings.useGit || repository ||
415 !FirstRunWizard::isStore(clean)));
416 updateStatus();
417}
418
419auto StoreWizardPage::recipients() const -> int {
420 int ticked = 0;
421 for (const UserInfo &key : std::as_const(m_wizard->m_keys)) {
422 ticked += key.enabled ? 1 : 0;
423 }
424 return ticked;
425}
426
428 const QString path = QDir::cleanPath(m_path->text().trimmed());
429 QString text;
430 if (path.isEmpty()) {
431 text = tr("Enter a folder.");
432 } else if (FirstRunWizard::isStore(path)) {
433 const int entries = static_cast<int>(
434 Util::regularFilesUnder(path, {QStringLiteral("*.gpg")}).size());
435 text = tr("An existing password store with %n entries.", nullptr, entries);
436 } else if (QDir(path).exists()) {
437 text = QDir(path).isEmpty()
438 ? tr("An empty folder; it will be set up as a password store.")
439 : tr("This folder is not a password store yet; a .gpg-id for "
440 "the ticked keys will be written into it.");
441 } else {
442 text = tr("The folder does not exist yet; it will be created.");
443 }
444 if (!path.isEmpty() && !FirstRunWizard::isStore(path) && recipients() == 0) {
445 text += QLatin1Char(' ') +
446 tr("Go back and tick at least one key to encrypt it to.");
447 }
448 m_status->setText(text);
449 emit completeChanged();
450}
451
452auto StoreWizardPage::isComplete() const -> bool {
453 const QString path = QDir::cleanPath(m_path->text().trimmed());
454 if (path.isEmpty()) {
455 return false;
456 }
457 return FirstRunWizard::isStore(path) || recipients() > 0;
458}
459
461 m_wizard->m_settings.passStore =
462 QDir::fromNativeSeparators(m_path->text().trimmed());
463 m_wizard->m_settings.useGit = m_useGit->isEnabled() && m_useGit->isChecked();
464 return true;
465}
466
467// --------------------------------------------------------------------------
468
470 : QWizardPage(wizard), m_wizard(wizard), m_summary(new QLabel(this)),
471 m_hidePassword(new QCheckBox(tr("Hide passwords until asked"), this)),
472 m_trayIcon(new QCheckBox(tr("Show an icon in the system tray"), this)) {
473 setTitle(tr("Ready"));
474 setSubTitle(tr("Everything else can be changed later in Settings."));
475 auto *layout = new QVBoxLayout(this);
476 m_summary->setWordWrap(true);
477 m_summary->setTextFormat(Qt::RichText);
478 layout->addWidget(m_summary);
479 layout->addSpacing(12);
480 m_hidePassword->setToolTip(
481 tr("Show the password line as dots; Show password reveals it"));
482 layout->addWidget(m_hidePassword);
483 layout->addWidget(m_trayIcon);
484 layout->addStretch();
485 setFinalPage(true);
486}
487
489 const AppSettings &s = m_wizard->m_settings;
490 const QString store = QDir::toNativeSeparators(QDir::cleanPath(s.passStore));
491 QStringList lines;
492 lines << tr("Store: %1").arg(store.toHtmlEscaped());
494 lines << tr("It is already a password store and is used as it is.");
495 } else if (s.useGit) {
496 lines << tr("It will be set up for the ticked keys and put under Git.");
497 } else {
498 lines << tr("It will be set up for the ticked keys.");
499 }
500 lines << tr("GnuPG: %1").arg(s.gpgExecutable.toHtmlEscaped());
501 lines << (s.usePass ? tr("Operations run through pass.")
502 : tr("Operations run through gpg and git directly."));
503 m_summary->setText(lines.join(QStringLiteral("<br>")));
504 m_hidePassword->setChecked(true);
505 const bool tray = QSystemTrayIcon::isSystemTrayAvailable();
506 m_trayIcon->setVisible(tray);
507 m_trayIcon->setChecked(tray && s.useTrayIcon);
508}
509
511 m_wizard->m_settings.hidePassword = m_hidePassword->isChecked();
512 m_wizard->m_settings.useTrayIcon =
513 m_trayIcon->isVisible() && m_trayIcon->isChecked();
514 return true;
515}
QLabel * m_summary
Store, what Finish does with it, gpg, mode.
void initializePage() override
Fill the summary from the settings.
QCheckBox * m_trayIcon
Tray icon; hidden when there is no tray.
FirstRunWizard * m_wizard
Owner; its settings get the preferences.
QCheckBox * m_hidePassword
Show the password line as dots; on.
DoneWizardPage(FirstRunWizard *wizard)
Build the page.
auto validatePage() -> bool override
Store the preferences.
static auto executeBlocking(const QString &app, const QStringList &args, const QString &input=QString(), QString *process_out=nullptr, QString *process_err=nullptr) -> int
Run a command synchronously and return its exit code.
Definition executor.cpp:422
static auto parseWslCommand(const QString &app) -> std::optional< WslCommand >
Parse a configured executable as a WSL command.
Definition executor.cpp:114
The first start of QtPass: find GnuPG (and pass and Git), make sure there is a secret key,...
@ DonePage
DoneWizardPage: summary and two preferences.
@ ProgramsPage
ProgramsWizardPage: where gpg, pass and git are.
@ KeyPage
KeyWizardPage: the secret keys, with generation.
@ StorePage
StoreWizardPage: the store folder and the Git choice.
@ IntroPage
What QtPass is and what the wizard will do.
auto initialiseStore(const QString &store) -> bool
Make store a password store for the ticked keys: create the folder when it is missing,...
void accept() override
Write the settings and, when the chosen store has no .gpg-id yet, create and initialise it for the ch...
auto initialiseGit(const QString &store) -> bool
Give an existing store the Git repository it was chosen to have.
static auto isStore(const QString &path) -> bool
Whether path points at an initialised store.
AppSettings m_settings
FirstRunWizard(QWidget *parent=nullptr)
Build the wizard from the current settings.
QList< UserInfo > m_keys
static auto secretKeys(const QString &gpgExecutable) -> QList< UserInfo >
The secret keys gpg lists for gpgExecutable.
Implementation that imitates 'pass' when the real tool is unavailable.
Definition imitatepass.h:40
QListWidget * m_list
One checkable row per secret key.
void initializePage() override
List the secret keys for the gpg chosen on the previous page.
void reload()
List the secret keys of the chosen gpg into the wizard and the widget, ticking every usable key and g...
auto isComplete() const -> bool override
Always passable: an existing store needs no recipient choice here (and its key may not be imported ye...
QPushButton * m_generate
Opens KeygenDialog.
void generate()
Run KeygenDialog on a private ImitatePass built from the wizard's settings, so the chosen gpg is used...
FirstRunWizard * m_wizard
Owner; holds the key list this page fills.
QLabel * m_status
Says so when gpg has no secret key yet.
auto validatePage() -> bool override
Remember which keys are enabled.
KeyWizardPage(FirstRunWizard *wizard)
Build the page.
Handles GPG keypair generation.
static void invalidate()
Forget the currently active backend.
void init(const AppSettings &settings)
Initialize the Pass instance with a settings snapshot.
Definition pass.cpp:158
static auto initGit(const QString &dir, const AppSettings &s, QString *note) -> bool
Put an existing store under Git: git init, stage the .gpg entries and the .gpg-id files (nothing else...
static auto gitIdentityConfigured(const QString &dir, const AppSettings &s) -> bool
Whether git has a name and e-mail to commit with, i.e. user.name and user.email resolve (globally or ...
static auto initialise(const QString &dir, const QList< UserInfo > &users, const AppSettings &s, bool useGit, QString *note) -> bool
Write the recipients and, if wanted, put the folder under git.
QLineEdit * m_gpg
The gpg executable; required.
QLineEdit * m_pass
The pass executable; optional.
QLineEdit * m_git
The git executable; optional.
FirstRunWizard * m_wizard
Owner; its settings are what the page edits.
void initializePage() override
Fill the fields from the settings (autodetection already ran).
QLabel * m_gpgStatus
Under the gpg field: why it cannot be used.
void updateStatus()
After an edit: say under the gpg field when it is missing or not executable, allow "use pass" only fo...
auto isComplete() const -> bool override
Whether the gpg path points at something that can run.
ProgramsWizardPage(FirstRunWizard *wizard)
Build the page.
auto validatePage() -> bool override
Store the paths and the pass/Git choices in the settings.
QCheckBox * m_usePass
Run pass instead of gpg/git; needs m_pass.
Singleton that stores QtPass settings, handles persistence and defaults.
static void save(const AppSettings &settings)
Persist an AppSettings value object to the singleton store.
StoreWizardPage(FirstRunWizard *wizard)
Build the page.
QLabel * m_status
What Finish will do with the folder.
auto validatePage() -> bool override
Store the path and the Git choice.
void initializePage() override
Show the configured store path.
auto recipients() const -> int
How many keys are ticked on the key page.
auto isComplete() const -> bool override
Whether a path is given and, for a folder that is not a store yet, at least one key was ticked to enc...
QLineEdit * m_path
The store folder, in native separators.
QCheckBox * m_useGit
Keep the store under Git; off without git.
FirstRunWizard * m_wizard
Owner; its settings get the path and flag.
void updateStatus()
After an edit: describe the folder (existing store with its entry count, empty folder,...
static auto regularFilesUnder(const QString &dir, const QStringList &nameFilters, QStringList *skipped=nullptr, bool hiddenFiles=false) -> QStringList
Regular files under dir whose name matches one of nameFilters, found by walking only real directories...
Definition util.cpp:721
static auto findPasswordStore() -> QString
Locate the password store directory.
Definition util.cpp:73
static auto mkpathForStore(const QString &path) -> bool
Create path and any missing parents as pass would.
Definition util.cpp:438
static auto normalizeFolderPath(const QString &path) -> QString
Ensure a folder path always ends with '/'.
Definition util.cpp:98
static auto findBinaryInPath(const QString &binary) -> QString
Locate an executable by searching the process PATH and (on Windows) falling back to WSL.
Definition util.cpp:474
Executor, the FIFO queue every gpg, git and pass child process of QtPass goes through,...
The first-run wizard and its four pages: programs, key, store and summary.
auto parseGpgColonOutput(const QString &output, bool secret) -> QList< UserInfo >
Parse a gpg colon listing into one UserInfo per key.
Parsing of gpg --with-colons key listings into UserInfo records.
ImitatePass, the backend that does what pass does by running gpg and git itself.
Dialog that collects name, e-mail and passphrase for a new GPG keypair and drives gpg --gen-key --bat...
PassBackendFactory, which constructs the two Pass backends on demand and hands out the one the settin...
Initialising a new profile's store (.gpg-id, signature, Git) with blocking processes,...
QtPassSettings, the QSettings singleton behind every saved setting, the profiles and the Pass backend...
Plain value-object holding all flat QtPass configuration.
Definition appsettings.h:36
QString gpgExecutable
Path to the gpg/gpg2 executable.
Definition appsettings.h:66
bool usePass
Use the pass CLI (true) or native git/gpg.
Definition appsettings.h:59
QString pwgenExecutable
Path to the pwgen executable.
Definition appsettings.h:67
QString passStore
Password-store root path.
Definition appsettings.h:60
QString gitExecutable
Path to the git executable.
Definition appsettings.h:65
bool useTrayIcon
Show a system tray icon.
QString passExecutable
Path to the pass executable.
Definition appsettings.h:64
bool useGit
Enable git integration.
Stores GPG key info including validity, creation date, and more.
Definition userinfo.h:19
bool enabled
Whether this user/key is enabled for normal use.
Definition userinfo.h:65
QString key_id
Hexadecimal representation of the GnuPG key identifier.
Definition userinfo.h:52
auto isValid() const -> bool
Check whether the key has any usable validity level.
Definition userinfo.h:43
QString name
GPG user ID / full name.
Definition userinfo.h:48
QDateTime expiry
Date/time when key expires.
Definition userinfo.h:69
Util: static helpers for the process environment, binary discovery, store paths, URL safety and the l...