QtPass 2.0.0
Developer documentation
Loading...
Searching...
No Matches
configdialog.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2014 Anne Jan Brouwer
2// SPDX-License-Identifier: GPL-3.0-or-later
10#include "configdialog.h"
11#include "appsettings.h"
12#include "executor.h"
13#include "gpgkeystate.h"
14#include "keygendialog.h"
15#include "profileinit.h"
16#include "qtpasssettings.h"
17#include "sshauthsock.h"
18#include "ui_configdialog.h"
19#include "usersdialog.h"
20#include "util.h"
21#include "windowstatestore.h"
22#include <QClipboard>
23#include <QDir>
24#include <QEvent>
25#include <QFileDialog>
26#include <QFileInfo>
27#include <QFormLayout>
28#include <QGuiApplication>
29#include <QHash>
30#include <QLabel>
31#include <QListWidgetItem>
32#include <QMessageBox>
33#include <QPushButton>
34#include <QRegularExpression>
35#include <QSet>
36#include <QSystemTrayIcon>
37#include <algorithm>
38#include <utility>
39#ifdef Q_OS_WIN
40#include <windows.h>
41#endif
42
43#include "qtpasslogging.h"
44
46 : QDialog(parent), ui(new Ui::ConfigDialog) {
47 ui->setupUi(this);
48
49 WindowStateStore::attach(*this, QStringLiteral("configDialog"));
50
51 // setSpecialValueText() in retranslateUi() drops the size hint without
52 // telling the layout, which then sizes the box for "0" and shows "ver".
53 ui->spinBoxAutoclearSeconds->updateGeometry();
54 ui->spinBoxAutoclearPanelSeconds->updateGeometry();
55
56 // The secret field names only mean something while passwords are hidden.
57 connect(ui->checkBoxHidePassword, &QCheckBox::toggled, this,
58 [this] { updateSecretFieldsEnabled(); });
59 connect(ui->checkBoxHideSecretFields, &QCheckBox::toggled, this,
60 [this] { updateSecretFieldsEnabled(); });
61 connect(ui->checkBoxHideAllFields, &QCheckBox::toggled, this,
62 [this] { updateSecretFieldsEnabled(); });
63 connect(ui->buttonDefaultSecretFields, &QPushButton::clicked, this, [this] {
64 ui->lineEditSecretFields->setText(
65 AppSettings::defaultSecretFieldNames().join(QStringLiteral(", ")));
66 });
67 // "Seconds" says nothing next to "Never" (the spin box's 0).
68 // Restoring is part of clearing: only there when the clipboard is cleared.
69 connect(ui->spinBoxAutoclearSeconds, &QSpinBox::valueChanged, this,
70 [this](int value) {
71 ui->labelSeconds->setVisible(value > 0);
72 ui->checkBoxRestoreClipboard->setVisible(value > 0);
73 });
74 connect(ui->spinBoxAutoclearPanelSeconds, &QSpinBox::valueChanged, this,
75 [this](int value) { ui->labelPanelSeconds->setVisible(value > 0); });
76 connect(
77 ui->comboBoxFields, &QComboBox::currentIndexChanged, this,
78 [this](int index) { ui->plainTextEditTemplate->setEnabled(index > 0); });
79
82
83 if (!QSystemTrayIcon::isSystemTrayAvailable()) {
84 ui->checkBoxUseTrayIcon->setEnabled(false);
85 ui->checkBoxUseTrayIcon->setToolTip(tr("System tray is not available"));
86 ui->checkBoxHideOnClose->setEnabled(false);
87 ui->checkBoxStartMinimized->setEnabled(false);
88 }
89
90 // Wayland has no client-side "keep above" — Qt::WindowStaysOnTopHint is
91 // silently ignored there (stacking belongs to the compositor). Say so
92 // instead of offering a checkbox that does nothing.
93 if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) {
94 ui->checkBoxAlwaysOnTop->setEnabled(false);
95 ui->checkBoxAlwaysOnTop->setToolTip(
96 tr("Not available on Wayland; use your compositor's "
97 "\"keep above\" window rule instead"));
98 }
99
100#if defined(Q_OS_WIN)
101 // checkBoxUseOtp stays visible: OTP is generated in-process, not by the
102 // Unix-only pass-otp extension.
103 ui->checkBoxUseQrencode->hide();
104#endif
105
107 ui->checkBoxUseQrencode->setEnabled(false);
108 ui->checkBoxUseQrencode->setToolTip(tr("qrencode needs to be installed"));
109 }
110
111 connect(ui->profileList, &QListWidget::currentRowChanged, this,
113 connect(ui->profileName, &QLineEdit::textEdited, this,
115 connect(ui->profilePath, &QLineEdit::textEdited, this,
117 connect(ui->profileSigningKey, &QLineEdit::textEdited, this,
119 for (QCheckBox *box :
120 {ui->profileUseGit, ui->profileAutoPush, ui->profileAutoPull}) {
121 connect(box, &QCheckBox::clicked, this, &ConfigDialog::onProfileGitToggled);
122 }
123 m_nameTip = ui->profileName->toolTip();
124 m_pathTip = ui->profilePath->toolTip();
125 m_keyTip = ui->profileSigningKey->toolTip();
126 // A warning, not a problem: shown under the signing key while another
127 // profile, for another store, signs with the same key (#2050).
128 m_sharedKeyWarning = new QLabel(this);
129 m_sharedKeyWarning->setObjectName(QStringLiteral("profileSharedKeyWarning"));
130 m_sharedKeyWarning->setTextFormat(Qt::PlainText);
131 m_sharedKeyWarning->setWordWrap(true);
132 m_sharedKeyWarning->hide();
133 {
134 int row = -1;
135 QFormLayout::ItemRole role = QFormLayout::FieldRole;
136 ui->profileFormLayout->getWidgetPosition(ui->profileSigningKey, &row,
137 &role);
138 ui->profileFormLayout->insertRow(row + 1, QString(), m_sharedKeyWarning);
139 }
140 // Under the pass settings: a store pass would commit outside of.
141 m_passGitWarning = new QLabel(this);
142 m_passGitWarning->setObjectName(QStringLiteral("passGitWarning"));
143 m_passGitWarning->setTextFormat(Qt::PlainText);
144 m_passGitWarning->setWordWrap(true);
145 m_passGitWarning->hide();
146 ui->groupBoxPass->layout()->addWidget(m_passGitWarning);
147 connect(ui->storePath, &QLineEdit::textChanged, this,
148 [this] { updatePassGitWarning(); });
150
151 ui->label->setText(ui->label->text() + VERSION);
152 ui->comboBoxClipboard->clear();
153
154 ui->comboBoxClipboard->addItem(tr("No clipboard"));
155 ui->comboBoxClipboard->addItem(tr("Always copy to clipboard"));
156 ui->comboBoxClipboard->addItem(tr("On-demand copy to clipboard"));
157
158 int currentIndex = static_cast<int>(s.clipBoardType);
159 ui->comboBoxClipboard->setCurrentIndex(currentIndex);
160 on_comboBoxClipboard_activated(currentIndex);
161
162 QClipboard *clip = QApplication::clipboard();
163 if (!clip->supportsSelection()) {
164 useSelection(false);
165 ui->checkBoxSelection->setVisible(false);
166 } else {
168 }
169
170 connect(ui->pageList, &QListWidget::currentRowChanged, ui->pages,
171 &QStackedWidget::setCurrentIndex);
172 ui->pageList->setCurrentRow(
174 ? 0
175 // Programs is what needs fixing when the configuration is not valid.
176 : ui->pages->indexOf(ui->pagePrograms));
177
178 connect(this, &ConfigDialog::accepted, this, &ConfigDialog::on_accepted);
179}
180
182 ui->passPath->setText(settings.passExecutable);
183 setGitPath(settings.gitExecutable);
184 ui->gpgPath->setText(settings.gpgExecutable);
185 ui->storePath->setText(settings.passStore);
186 ui->sshAuthSockOverride->setText(settings.sshAuthSockOverride);
187
188 // One control each: 0 is "Never", so off and a delay are one setting.
189 ui->spinBoxAutoclearSeconds->setValue(
190 settings.useAutoclear ? settings.autoclearSeconds : 0);
191 ui->labelSeconds->setVisible(ui->spinBoxAutoclearSeconds->value() > 0);
192 ui->checkBoxRestoreClipboard->setChecked(settings.restoreClipboard);
193 ui->checkBoxRestoreClipboard->setVisible(
194 ui->spinBoxAutoclearSeconds->value() > 0);
195 ui->spinBoxAutoclearPanelSeconds->setValue(
196 settings.useAutoclearPanel ? settings.autoclearPanelSeconds : 0);
197 ui->labelPanelSeconds->setVisible(ui->spinBoxAutoclearPanelSeconds->value() >
198 0);
199 ui->checkBoxHidePassword->setChecked(settings.hidePassword);
200 ui->checkBoxHideSecretFields->setChecked(settings.hideSecretFields);
201 ui->checkBoxHideAllFields->setChecked(settings.hideAllFields);
202 ui->lineEditSecretFields->setText(
203 settings.secretFieldNames.join(QStringLiteral(", ")));
205 ui->checkBoxHideContent->setChecked(settings.hideContent);
206 ui->checkBoxUseMonospace->setChecked(settings.useMonospace);
207 ui->checkBoxDisplayAsIs->setChecked(settings.displayAsIs);
208 ui->checkBoxNoLineWrapping->setChecked(settings.noLineWrapping);
209 ui->checkBoxAddGPGId->setChecked(settings.addGPGId);
210 ui->checkBoxHideOnClose->setChecked(settings.hideOnClose);
211 ui->checkBoxStartMinimized->setChecked(settings.startMinimized);
212
213 ui->checkBoxAvoidCapitals->setChecked(settings.avoidCapitals);
214 ui->checkBoxAvoidNumbers->setChecked(settings.avoidNumbers);
215 ui->checkBoxLessRandom->setChecked(settings.lessRandom);
216 ui->checkBoxUseSymbols->setChecked(settings.useSymbols);
217 ui->plainTextEditTemplate->setPlainText(settings.passTemplate);
218 // All fields only applies while templating is on (#1766), so the two
219 // switches are one choice of three.
220 ui->comboBoxFields->setCurrentIndex(
221 !settings.useTemplate ? 0 : (settings.templateAllFields ? 2 : 1));
222 ui->plainTextEditTemplate->setEnabled(settings.useTemplate);
223 ui->checkBoxAutoPull->setChecked(settings.autoPull);
224 ui->checkBoxAutoPush->setChecked(settings.autoPush);
225 ui->checkBoxGitUseParentRepository->setChecked(
226 settings.gitUseParentRepository);
227 ui->checkBoxAlwaysOnTop->setChecked(settings.alwaysOnTop);
228
229 // Dependent helpers: set after the plain values above so the enable/disable
230 // logic they trigger sees the final widget states.
231 usePass(settings.usePass);
232 useTrayIcon(settings.useTrayIcon);
233 useGit(settings.useGit);
234 useOtp(settings.useOtp);
236 useQrencode(settings.useQrencode);
237 // Before usePwgen (gated on the pwgen path); otherwise readSettings() writes
238 // the .ui defaults over the saved configuration on every OK.
241 usePwgen(settings.usePwgen);
242}
243
245 // Start from the persisted settings so keys this dialog does not own
246 // (window geometry, profiles, etc.) survive the save.
248
249 settings.passExecutable = ui->passPath->text();
250 settings.gitExecutable = ui->gitPath->text();
251 settings.gpgExecutable = ui->gpgPath->text();
252 settings.sshAuthSockOverride = ui->sshAuthSockOverride->text().trimmed();
253 settings.passStore = Util::normalizeFolderPath(ui->storePath->text());
254 settings.usePass = ui->radioButtonPass->isChecked();
255 settings.clipBoardType =
256 static_cast<Enums::ClipBoardType>(ui->comboBoxClipboard->currentIndex());
257 settings.useSelection = ui->checkBoxSelection->isChecked();
258 settings.autoclearSeconds = ui->spinBoxAutoclearSeconds->value();
259 settings.useAutoclear = settings.autoclearSeconds > 0;
260 settings.restoreClipboard = ui->checkBoxRestoreClipboard->isChecked();
261 settings.autoclearPanelSeconds = ui->spinBoxAutoclearPanelSeconds->value();
262 settings.useAutoclearPanel = settings.autoclearPanelSeconds > 0;
263 settings.hidePassword = ui->checkBoxHidePassword->isChecked();
264 settings.hideSecretFields = ui->checkBoxHideSecretFields->isChecked();
265 settings.hideAllFields = ui->checkBoxHideAllFields->isChecked();
266 // As written, an empty list included ("Default" puts the set back).
267 settings.secretFieldNames.clear();
268 for (const QString &name :
269 ui->lineEditSecretFields->text().split(QLatin1Char(','))) {
270 if (!name.trimmed().isEmpty()) {
271 settings.secretFieldNames << name.trimmed();
272 }
273 }
274 settings.hideContent = ui->checkBoxHideContent->isChecked();
275 settings.useMonospace = ui->checkBoxUseMonospace->isChecked();
276 settings.displayAsIs = ui->checkBoxDisplayAsIs->isChecked();
277 settings.noLineWrapping = ui->checkBoxNoLineWrapping->isChecked();
278 settings.addGPGId = ui->checkBoxAddGPGId->isChecked();
279 // A disabled control (e.g. no system tray) keeps the persisted value
280 // instead of clobbering it to false.
281 if (ui->checkBoxUseTrayIcon->isEnabled()) {
282 settings.useTrayIcon = ui->checkBoxUseTrayIcon->isChecked();
283 }
284 if (ui->checkBoxHideOnClose->isEnabled()) {
285 settings.hideOnClose = ui->checkBoxHideOnClose->isChecked();
286 }
287 if (ui->checkBoxStartMinimized->isEnabled()) {
288 settings.startMinimized = ui->checkBoxStartMinimized->isChecked();
289 }
290 settings.useGit = ui->checkBoxUseGit->isChecked();
291 // The backends and the auto-pull-on-start read the global keys; #1140
292 // started storing these per profile as well and stopped writing the
293 // globals, which left both checkboxes without effect.
294 settings.autoPush = ui->checkBoxAutoPush->isChecked();
295 settings.autoPull = ui->checkBoxAutoPull->isChecked();
296 settings.gitUseParentRepository =
297 ui->checkBoxGitUseParentRepository->isChecked();
298 settings.useOtp = ui->checkBoxUseOtp->isChecked();
299 settings.useGrepSearch = ui->checkBoxUseGrepSearch->isChecked();
300 settings.useQrencode = ui->checkBoxUseQrencode->isChecked();
301 settings.pwgenExecutable = ui->pwgenPath->text();
302 settings.usePwgen = ui->checkBoxUsePwgen->isChecked();
303 settings.avoidCapitals = ui->checkBoxAvoidCapitals->isChecked();
304 settings.avoidNumbers = ui->checkBoxAvoidNumbers->isChecked();
305 settings.lessRandom = ui->checkBoxLessRandom->isChecked();
306 settings.useSymbols = ui->checkBoxUseSymbols->isChecked();
308 settings.useTemplate = ui->comboBoxFields->currentIndex() > 0;
309 settings.passTemplate = ui->plainTextEditTemplate->toPlainText();
310 settings.templateAllFields = ui->comboBoxFields->currentIndex() == 2;
311 settings.alwaysOnTop = ui->checkBoxAlwaysOnTop->isChecked();
312 settings.version = VERSION;
313
314 return settings;
315}
316
317ConfigDialog::~ConfigDialog() = default;
318
319void ConfigDialog::setGitPath(const QString &path) {
320 ui->gitPath->setText(path);
321 ui->checkBoxUseGit->setEnabled(!path.isEmpty());
322 if (path.isEmpty()) {
323 useGit(false);
324 }
325}
326
328 ui->radioButtonNative->setChecked(!usePass);
329 ui->radioButtonPass->setChecked(usePass);
331}
332
333auto ConfigDialog::isFingerprintList(const QString &setting) -> bool {
334 // 40 hex characters for OpenPGP v4 keys, 64 for v5/v6 (gpg 2.5+).
335 static const QRegularExpression fingerprint(
336 QStringLiteral("^(?:[0-9A-Fa-f]{40}|[0-9A-Fa-f]{64})$"));
337 const QStringList keys = setting.split(QLatin1Char(' '), Qt::SkipEmptyParts);
338 return std::all_of(keys.cbegin(), keys.cend(), [](const QString &key) {
339 return fingerprint.match(key).hasMatch();
340 });
341}
342
343// Profile names must be unique: the settings key profiles by name, so a
344// duplicate would silently overwrite the other.
346 const QHash<QString, int> &names) const
347 -> ProfileProblems {
348 ProfileProblems problems;
349 if (entry.name.isEmpty()) {
350 problems.name = tr("This field is required");
351 } else if (names.value(entry.name) > 1) {
352 problems.name = tr("Another profile already has this name");
353 }
354 if (entry.profile.path.isEmpty()) {
355 problems.path = tr("This field is required");
356 }
357 // pass compares PASSWORD_STORE_SIGNING_KEY against the fingerprints in
358 // gpg's VALIDSIG line, so anything shorter than a fingerprint would sign
359 // and then never verify. Refuse it here instead of failing at first use.
360 if (!isFingerprintList(entry.profile.signingKey)) {
361 problems.key = tr("Full key fingerprints only (40 or 64 hexadecimal "
362 "characters), separated by spaces");
363 }
364 return problems;
365}
366
367void ConfigDialog::showProblems(int row, const ProfileProblems &problems) {
368 if (QListWidgetItem *item = ui->profileList->item(row)) {
369 item->setBackground(problems.none() ? QBrush() : QBrush(Qt::red));
370 item->setToolTip(problems.none() ? problems.sharedKey : problems.first());
371 }
372 if (row != m_currentEntry) {
373 return;
374 }
375 m_sharedKeyWarning->setText(problems.sharedKey);
376 m_sharedKeyWarning->setVisible(!problems.sharedKey.isEmpty());
377 const auto tipOr = [](const QString &problem, const QString &tip) {
378 return problem.isEmpty() ? tip : problem;
379 };
380 ui->profileName->setToolTip(tipOr(problems.name, m_nameTip));
381 ui->profilePath->setToolTip(tipOr(problems.path, m_pathTip));
382 ui->profileSigningKey->setToolTip(tipOr(problems.key, m_keyTip));
383}
384
386 // One folder spelled two ways (through a link, another case on a
387 // case-insensitive file system) is one store: compared as the file system
388 // resolves it where it exists. Resolved once per validate(), not once per
389 // pair of profiles.
390 SigningStores resolved;
391 // Keys are looked up in the keyring that signs: the configured GPG home,
392 // as profiles sign with it, not whatever GNUPGHOME QtPass inherited. The
393 // dialog does not edit the home, so the settings are read once, not on
394 // every keystroke; the environment is built each time, as the home may
395 // only appear meanwhile (a key generated from this dialog).
396 if (!m_gpgSettings) {
398 }
399 const QProcessEnvironment env = ProfileInit::gpgEnvironment(*m_gpgSettings);
400 for (const ProfileEntry &entry : std::as_const(m_entries)) {
401 const QString real = QFileInfo(entry.profile.path).canonicalFilePath();
402 resolved.stores << (real.isEmpty() ? QDir::cleanPath(entry.profile.path)
403 : real);
404 resolved.keys << signerKeys(entry.profile.signingKey, env);
405 }
406 return resolved;
407}
408
409auto ConfigDialog::signerKeys(const QString &signingKey,
410 const QProcessEnvironment &env) const
411 -> QSet<QString> {
412 // Verification takes the primary or the signing subkey's fingerprint
413 // from VALIDSIG, and gpg signs for a primary key with its subkey: a key
414 // written as the subkey in one profile and as the primary in another is
415 // one signer (#2087). Only complete IDs are asked about, so typing a key
416 // does not start gpg for every prefix.
417 static const QRegularExpression complete(QStringLiteral(
418 "^(?:0X)?(?:[0-9A-F]{8}|[0-9A-F]{16}|[0-9A-F]{40}|[0-9A-F]{64})$"));
419 const QString gpg = ui->gpgPath->text();
420 QSet<QString> keys;
421 for (const QString &key :
422 signingKey.toUpper().split(QLatin1Char(' '), Qt::SkipEmptyParts)) {
423 keys.insert(key);
424 if (gpg.isEmpty() || !complete.match(key).hasMatch()) {
425 continue;
426 }
427 const QString cacheKey = gpg + QLatin1Char('\n') +
428 env.value(QStringLiteral("GNUPGHOME")) +
429 QLatin1Char('\n') + key;
430 auto cached = m_primaryOf.constFind(cacheKey);
431 if (cached == m_primaryOf.cend()) {
432 QString out;
433 // Blocking, once per key: no trustdb check, which a large keyring
434 // would make slow.
435 const int rc = Executor::executeBlocking(
436 env, gpg,
437 {QStringLiteral("--no-tty"), QStringLiteral("--batch"),
438 QStringLiteral("--no-auto-check-trustdb"),
439 QStringLiteral("--with-colons"),
440 QStringLiteral("--with-fingerprint"),
441 QStringLiteral("--with-subkey-fingerprint"),
442 QStringLiteral("--list-keys"), QStringLiteral("--"), key},
443 &out);
444 cached = m_primaryOf.insert(
445 cacheKey, rc == 0 ? primaryFingerprintOf(out, key) : QString());
446 }
447 if (!cached->isEmpty()) {
448 keys.insert(*cached);
449 }
450 }
451 return keys;
452}
453
454void ConfigDialog::changeEvent(QEvent *event) {
455 QDialog::changeEvent(event);
456 if (event->type() != QEvent::ActivationChange) {
457 return;
458 }
459 // A key is imported somewhere else: a terminal, a key manager, or the
460 // key generator this dialog opens. Whatever keyring gpg uses, the dialog
461 // is in the background meanwhile, so a key gpg did not know is asked
462 // about again on return.
463 if (!isActiveWindow()) {
464 const auto forgotten = m_primaryOf.removeIf(
465 [](const std::pair<const QString &, QString &> &lookup) {
466 return lookup.second.isEmpty();
467 });
468 m_recheckSigners = m_recheckSigners || forgotten > 0;
469 } else if (m_recheckSigners) {
470 m_recheckSigners = false;
471 validate();
472 }
473}
474
475auto ConfigDialog::sharingTheKey(int row, const SigningStores &resolved) const
476 -> QStringList {
477 // A list signed for one store verifies in the other when the stores share
478 // a signing key; the folder line names a folder, not a store (#2050).
479 QStringList others;
480 const QSet<QString> &mine = resolved.keys.at(row);
481 if (mine.isEmpty()) {
482 return others;
483 }
484 for (int other = 0; other < m_entries.size(); ++other) {
485 if (other == row || resolved.stores.at(other) == resolved.stores.at(row)) {
486 continue;
487 }
488 if (resolved.keys.at(other).intersects(mine)) {
489 others << m_entries.at(other).name;
490 }
491 }
492 others.sort();
493 return others;
494}
495
497 QHash<QString, int> names;
498 for (const ProfileEntry &entry : std::as_const(m_entries)) {
499 names[entry.name]++;
500 }
501 bool status = true;
502 const SigningStores resolved = signingStores();
503 for (int row = 0; row < m_entries.size(); ++row) {
504 ProfileProblems problems = problemsOf(m_entries.at(row), names);
505 if (const QStringList others = sharingTheKey(row, resolved);
506 !others.isEmpty()) {
507 problems.sharedKey =
508 tr("%1 signs with the same key for another store. A recipient "
509 "list signed for one of these stores is accepted in the other: "
510 "use a separate signing key for each store whose recipients "
511 "differ.")
512 .arg(others.join(QStringLiteral(", ")));
513 }
514 showProblems(row, problems);
515 status = status && problems.none();
516 }
517 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status);
519}
520
522 // Built after the first applySettings(); setProfiles() fills it then.
523 if (m_passGitWarning == nullptr) {
524 return;
525 }
526 QStringList warnings;
527 if (ui->radioButtonPass->isChecked()) {
528 QStringList stores{ui->storePath->text()};
529 for (const ProfileEntry &entry : std::as_const(m_entries)) {
530 stores << entry.profile.path;
531 }
532 stores.removeDuplicates();
533 for (const QString &store : std::as_const(stores)) {
534 const QString repository = Util::repositoryPassClimbsTo(store);
535 if (!repository.isEmpty()) {
536 warnings << tr("pass commits changes in %1 to the Git repository in "
537 "%2: with a colon in the store's path, pass cannot "
538 "keep Git to the store. Run \"git init\" in the store, "
539 "or use native Git/GPG.")
540 .arg(QDir::toNativeSeparators(store),
541 QDir::toNativeSeparators(repository));
542 }
543 }
544 }
545 m_passGitWarning->setText(warnings.join(QStringLiteral("\n\n")));
546 m_passGitWarning->setVisible(!warnings.isEmpty());
547}
548
550 const QString override = ui->sshAuthSockOverride->text().trimmed();
551 if (override.isEmpty()) {
552 return;
553 }
554 QString reason;
555 switch (SshAuthSock::overrideStatus(override)) {
557 return;
559 reason = tr("The path does not exist.");
560 break;
562 reason = tr("The path is not readable.");
563 break;
565 reason = tr("The path is not a Unix domain socket.");
566 break;
567 }
568 QMessageBox::warning(
569 this, tr("Potentially invalid SSH_AUTH_SOCK override"),
570 tr("The SSH_AUTH_SOCK override value may be invalid.\n\n%1\n\n"
571 "The value will still be saved as entered.")
572 .arg(reason));
573}
574
576 // As MainWindow::on_profileBox_currentTextChanged does when switching.
578 const QString wasActive = s.activeProfile;
579 QString nowActive;
580 for (const ProfileEntry &entry : std::as_const(m_entries)) {
581 if (!wasActive.isEmpty() && entry.originalName == wasActive) {
582 nowActive = entry.name;
583 }
584 }
585 s.activeProfile = nowActive;
586 const auto active = profiles.constFind(nowActive);
587 if (active != profiles.constEnd()) {
588 s.useGit = active->useGit.value_or(s.useGit);
589 s.autoPush = active->autoPush.value_or(s.autoPush);
590 s.autoPull = active->autoPull.value_or(s.autoPull);
591 }
593}
594
597 const Profiles existingProfiles = QtPassSettings::getProfiles();
598 // Persist via the facade, which also invalidates the cached Pass backend so
599 // a changed "use pass" mode takes effect.
601 // Profiles are not part of AppSettings yet, so persist them separately.
602 const Profiles profiles = getProfiles();
604 followActiveProfile(profiles);
605 initializeNewProfiles(existingProfiles);
606}
607
609 QString pass = Util::findBinaryInPath("pass");
610 if (!pass.isEmpty()) {
611 ui->passPath->setText(pass);
612 }
613 usePass(!pass.isEmpty());
614 QString gpg = Util::findBinaryInPath("gpg2");
615 if (gpg.isEmpty()) {
616 gpg = Util::findBinaryInPath("gpg");
617 }
618 if (!gpg.isEmpty()) {
619 ui->gpgPath->setText(gpg);
620 }
621 QString git = Util::findBinaryInPath("git");
622 if (!git.isEmpty()) {
623 ui->gitPath->setText(git);
624 }
625 QString pwgen = Util::findBinaryInPath("pwgen");
626 if (!pwgen.isEmpty()) {
627 ui->pwgenPath->setText(pwgen);
628 }
629}
630
632
634
636 bool state = ui->radioButtonPass->isChecked();
637 ui->groupBoxNative->setEnabled(!state);
638 ui->groupBoxPass->setEnabled(state);
641 if (state) {
642 ui->spinBoxPasswordLength->setEnabled(false);
643 ui->checkBoxUsePwgen->setEnabled(false);
644 ui->checkBoxAvoidCapitals->setEnabled(false);
645 ui->checkBoxUseSymbols->setEnabled(false);
646 ui->checkBoxLessRandom->setEnabled(false);
647 ui->checkBoxAvoidNumbers->setEnabled(false);
648 ui->labelPasswordChars->setEnabled(false);
649 ui->passwordCharTemplateSelector->setEnabled(false);
650 ui->lineEditPasswordChars->setEnabled(false);
651 } else {
652 // native mode: restore pwgen/charset state from existing handlers
653 ui->spinBoxPasswordLength->setEnabled(true);
654 ui->checkBoxUsePwgen->setEnabled(!ui->pwgenPath->text().isEmpty());
656 }
657}
658
660 QFileDialog dialog(this);
661 dialog.setFileMode(QFileDialog::ExistingFile);
662 dialog.setOption(QFileDialog::ReadOnly);
663 if (dialog.exec()) {
664 return dialog.selectedFiles().constFirst();
665 }
666
667 return {};
668}
669
670auto ConfigDialog::selectFolder() -> QString {
671 QFileDialog dialog(this);
672 dialog.setFileMode(QFileDialog::Directory);
673 dialog.setFilter(QDir::NoFilter);
674 dialog.setOption(QFileDialog::ShowDirsOnly);
675 if (dialog.exec()) {
676 return dialog.selectedFiles().constFirst();
677 }
678
679 return {};
680}
681
683 QString git = selectExecutable();
684 bool state = !git.isEmpty();
685 if (state) {
686 ui->gitPath->setText(git);
687 } else {
688 useGit(false);
689 }
690
691 ui->checkBoxUseGit->setEnabled(state);
692}
693
695 QString gpg = selectExecutable();
696 if (!gpg.isEmpty()) {
697 ui->gpgPath->setText(gpg);
698 }
699}
700
702 KeygenDialog d(ui->gpgPath->text(), QtPassSettings::getPass(), this);
703 d.exec();
704}
705
707 QString pass = selectExecutable();
708 if (!pass.isEmpty()) {
709 ui->passPath->setText(pass);
710 }
711}
712
714 QString store = selectFolder();
715 if (!store.isEmpty()) {
716 ui->storePath->setText(store);
717 }
718}
719
720// index 0 is "No clipboard".
722 bool state = index > 0;
723
724 ui->checkBoxSelection->setEnabled(state);
725 ui->checkBoxHidePassword->setEnabled(state);
727 ui->checkBoxHideContent->setEnabled(state);
728 ui->labelAutoclear->setEnabled(state);
729 ui->spinBoxAutoclearSeconds->setEnabled(state);
730 ui->labelSeconds->setEnabled(state);
731 ui->checkBoxRestoreClipboard->setEnabled(state);
732}
733
735 // Each control only while the one it depends on is on: other secrets and
736 // every field only while passwords are hidden; the secret words only while
737 // other secrets are, and not when every field is hidden anyway.
738 const bool hiding = ui->checkBoxHidePassword->isEnabled() &&
739 ui->checkBoxHidePassword->isChecked();
740 ui->checkBoxHideAllFields->setEnabled(hiding);
741 const bool all = hiding && ui->checkBoxHideAllFields->isChecked();
742 ui->checkBoxHideSecretFields->setEnabled(hiding && !all);
743 const bool words =
744 hiding && !all && ui->checkBoxHideSecretFields->isChecked();
745 ui->lineEditSecretFields->setEnabled(words);
746 ui->buttonDefaultSecretFields->setEnabled(words);
747}
748
750 ui->checkBoxSelection->setChecked(useSelection);
752}
753
755 on_comboBoxClipboard_activated(ui->comboBoxClipboard->currentIndex());
756}
757
759 const QString &currentProfile) {
760 // remove weird "" key value pairs
761 profiles.remove(QString());
762
763 m_entries.clear();
764 ui->profileList->clear();
765 int currentRow = -1;
766 for (auto i = profiles.cbegin(); i != profiles.cend(); ++i) {
767 if (i.key() == currentProfile) {
768 currentRow = m_entries.size();
769 }
770 m_entries.append({i.key(), i.value(), i.key()});
771 ui->profileList->addItem(i.key());
772 }
773 ui->profileList->setCurrentRow(currentRow);
774 if (currentRow < 0) {
775 loadProfileForm(-1);
776 }
777 validate();
778}
779
781 if (m_currentEntry < 0 || m_currentEntry >= m_entries.size()) {
782 return nullptr;
783 }
784 return &m_entries[m_currentEntry];
785}
786
787// The Git boxes show the profile's own flags, else the global ones; only a
788// click on a box makes the value the profile's own.
790 m_currentEntry = (row >= 0 && row < m_entries.size()) ? row : -1;
791 const ProfileEntry *entry = currentEntry();
792 m_loadingForm = true;
793 ui->profileForm->setEnabled(entry != nullptr);
794 ui->deleteButton->setEnabled(entry != nullptr);
795 ui->profileName->setText(entry ? entry->name : QString());
796 ui->profilePath->setText(entry ? entry->profile.path : QString());
797 ui->profileSigningKey->setText(entry ? entry->profile.signingKey : QString());
798 const bool useGit =
799 entry && entry->profile.useGit.value_or(ui->checkBoxUseGit->isChecked());
800 ui->profileUseGit->setChecked(useGit);
801 ui->profileAutoPush->setChecked(
802 entry &&
803 entry->profile.autoPush.value_or(ui->checkBoxAutoPush->isChecked()));
804 ui->profileAutoPull->setChecked(
805 entry &&
806 entry->profile.autoPull.value_or(ui->checkBoxAutoPull->isChecked()));
807 ui->profileAutoPush->setEnabled(useGit);
808 ui->profileAutoPull->setEnabled(useGit);
809 m_loadingForm = false;
811 validate();
812}
813
815 Profiles profiles;
816 for (const ProfileEntry &entry : std::as_const(m_entries)) {
817 profiles.insert(entry.name, entry.profile);
818 }
819 return profiles;
820}
821
822auto ConfigDialog::ensureProfileFolder(const QString &path) -> bool {
823 if (QDir(path).exists()) {
824 return true;
825 }
826 if (QMessageBox::question(
827 this, tr("Create profile directory?"),
828 tr("Would you like to create a password store at %1?").arg(path),
829 QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
830 return false;
831 }
832 if (!Util::mkpathForStore(path)) {
833 QMessageBox::warning(
834 this, tr("Error"),
835 tr("Could not create profile directory: %1").arg(path));
836 return false;
837 }
838 return true;
839}
840
842 const Profile &profile,
843 const QString &path) {
844 // Only the recipients come through the active backend. passStore is the
845 // profile, or getGpgIdPath() would pre-tick the active store's recipients;
846 // signing keys are per profile, exactly as the profile switch applies them
847 // (MainWindow::on_profileBox_currentTextChanged).
848 const AppSettings settings = QtPassSettings::load();
849 AppSettings profileSettings = settings;
850 profileSettings.passStore = Util::normalizeFolderPath(path);
851 profileSettings.passSigningKey = profile.signingKey;
852 // needsInit() found no .gpg-id: nothing to preselect, and none that
853 // appears meanwhile is taken (it would be read under the active
854 // profile's signing policy).
855 UsersDialog usersDialog(QtPassSettings::getPass(), profileSettings,
856 profileSettings.passStore,
858 usersDialog.setInitOnAccept(false);
859 usersDialog.setWindowTitle(tr("Select recipients for %1").arg(name));
860 if (usersDialog.exec() != QDialog::Accepted) {
861 return;
862 }
863 QString note;
864 const bool ok = ProfileInit::initialise(
865 path, usersDialog.selectedUsers(), profileSettings,
866 profile.useGit.value_or(settings.useGit), &note);
867 if (!ok) {
868 QMessageBox::warning(this, tr("Could not initialise profile %1").arg(name),
869 note);
870 } else if (!note.isEmpty()) {
871 QMessageBox::information(this, tr("Profile %1").arg(name), note);
872 }
873}
874
875void ConfigDialog::initializeNewProfiles(const Profiles &existingProfiles) {
876 const Profiles newProfiles = getProfiles();
877 // QMap iterates in name order.
878 for (auto it = newProfiles.cbegin(); it != newProfiles.cend(); ++it) {
879 const QString path = QDir::cleanPath(it.value().path);
880 // A store that was already a profile when the dialog opened, under this
881 // or another name: a rename is not a new store.
882 const bool known = std::any_of(
883 existingProfiles.cbegin(), existingProfiles.cend(),
884 [&it](const Profile &old) { return old.path == it.value().path; });
885 // needsInit is false for a missing folder, so it is created first.
886 if (!known && ensureProfileFolder(path) && ProfileInit::needsInit(path)) {
887 initialiseProfileStore(it.key(), it.value(), path);
888 }
889 }
890}
891
893 ProfileEntry entry;
894 entry.name = tr("New profile");
895 entry.profile.path = ui->storePath->text();
896 m_entries.append(entry);
897 ui->profileList->addItem(entry.name);
898 ui->profileList->setCurrentRow(m_entries.size() - 1);
899 ui->profileName->setFocus();
900 ui->profileName->selectAll();
901 validate();
902}
903
905 const QString dir = selectFolder();
906 if (!dir.isEmpty()) {
907 ui->profilePath->setText(dir);
909 }
910}
911
913 const int row = ui->profileList->currentRow();
914 if (row < 0 || row >= m_entries.size()) {
915 QMessageBox::warning(this, tr("No profile selected"),
916 tr("No profile selected to delete"));
917 return;
918 }
919 m_entries.removeAt(row);
920 delete ui->profileList->takeItem(row);
921 // currentRowChanged has re-pointed the form at the neighbour (or nothing).
922 validate();
923}
924
925void ConfigDialog::criticalMessage(const QString &title, const QString &text) {
926 QMessageBox::critical(this, title, text, QMessageBox::Ok, QMessageBox::Ok);
927}
928
929// Looks on Util's PATH (with the macOS and Windows additions) without a
930// subprocess; a hit is stored for the QR display, a miss leaves the setting.
931auto ConfigDialog::isQrencodeAvailable(const QString &configuredPath) -> bool {
932#ifdef Q_OS_WIN
933 Q_UNUSED(configuredPath);
934 return false;
935#else
936 if (!configuredPath.isEmpty()) {
937 const QFileInfo configured(configuredPath);
938 if (configured.isFile() && configured.isExecutable()) {
939 return true;
940 }
941 }
942 const QString found = Util::findBinaryInPath(QStringLiteral("qrencode"));
943 if (found.isEmpty()) {
944 return false;
945 }
947 return true;
948#endif
949}
950
951void ConfigDialog::useTrayIcon(bool useSystray) {
952 if (QSystemTrayIcon::isSystemTrayAvailable()) {
953 ui->checkBoxUseTrayIcon->setChecked(useSystray);
954 ui->checkBoxHideOnClose->setEnabled(useSystray);
955 ui->checkBoxStartMinimized->setEnabled(useSystray);
956
957 if (!useSystray) {
958 ui->checkBoxHideOnClose->setChecked(false);
959 ui->checkBoxStartMinimized->setChecked(false);
960 }
961 }
962}
963
965 bool state = ui->checkBoxUseTrayIcon->isChecked();
966 ui->checkBoxHideOnClose->setEnabled(state);
967 ui->checkBoxStartMinimized->setEnabled(state);
968}
969
971 ui->checkBoxUseGit->setChecked(useGit);
973}
974
976 ui->checkBoxUseOtp->setChecked(useOtp);
977}
978
980 ui->checkBoxUseGrepSearch->setChecked(useGrepSearch);
981}
982
984 ui->checkBoxUseQrencode->setChecked(useQrencode);
985}
986
988 ui->checkBoxAddGPGId->setEnabled(ui->checkBoxUseGit->isChecked());
989 ui->checkBoxAutoPull->setEnabled(ui->checkBoxUseGit->isChecked());
990 ui->checkBoxAutoPush->setEnabled(ui->checkBoxUseGit->isChecked());
991 // pass always stops git at the store's parent itself, so the choice only
992 // means something in native mode.
993 ui->checkBoxGitUseParentRepository->setEnabled(
994 ui->checkBoxUseGit->isChecked() && !ui->radioButtonPass->isChecked());
995}
996
998 QString pwgen = selectExecutable();
999 if (!pwgen.isEmpty()) {
1000 ui->pwgenPath->setText(pwgen);
1001 ui->checkBoxUsePwgen->setEnabled(true);
1002 } else {
1003 ui->checkBoxUsePwgen->setEnabled(false);
1004 ui->checkBoxUsePwgen->setChecked(false);
1005 }
1006}
1007
1008void ConfigDialog::setPwgenPath(const QString &pwgen) {
1009 ui->pwgenPath->setText(pwgen);
1010 if (pwgen.isEmpty()) {
1011 ui->checkBoxUsePwgen->setChecked(false);
1012 ui->checkBoxUsePwgen->setEnabled(false);
1013 }
1015}
1016
1018 if (ui->radioButtonPass->isChecked())
1019 return;
1020 bool usePwgen = ui->checkBoxUsePwgen->isChecked();
1021 ui->checkBoxAvoidCapitals->setEnabled(usePwgen);
1022 ui->checkBoxAvoidNumbers->setEnabled(usePwgen);
1023 ui->checkBoxLessRandom->setEnabled(usePwgen);
1024 ui->checkBoxUseSymbols->setEnabled(usePwgen);
1025 ui->lineEditPasswordChars->setEnabled(!usePwgen);
1026 ui->labelPasswordChars->setEnabled(!usePwgen);
1027 ui->passwordCharTemplateSelector->setEnabled(!usePwgen);
1028}
1029
1031 if (ui->pwgenPath->text().isEmpty()) {
1032 usePwgen = false;
1033 }
1034 ui->checkBoxUsePwgen->setChecked(usePwgen);
1036}
1037
1039 const PasswordConfiguration &config) {
1040 // Retain the custom charset so it survives even when a builtin set is the
1041 // active selection (the line edit then shows the builtin's characters).
1043 ui->spinBoxPasswordLength->setValue(config.length);
1044 ui->passwordCharTemplateSelector->setCurrentIndex(config.selected);
1046 ui->lineEditPasswordChars->setEnabled(false);
1047 }
1048 ui->lineEditPasswordChars->setText(config.Characters[config.selected]);
1049}
1050
1052 PasswordConfiguration config;
1053 config.length = ui->spinBoxPasswordLength->value();
1054 config.selected = static_cast<PasswordConfiguration::CharacterSet>(
1055 ui->passwordCharTemplateSelector->currentIndex());
1056 // For a builtin selection the line edit shows the builtin's characters;
1057 // reading it would overwrite the saved custom charset.
1060 ui->lineEditPasswordChars->text();
1061 } else {
1063 }
1064 return config;
1065}
1066
1068 ui->lineEditPasswordChars->setText(
1069 QtPassSettings::getPasswordConfiguration().Characters[index]);
1070 if (index == PasswordConfiguration::CUSTOM) {
1071 ui->lineEditPasswordChars->setEnabled(true);
1072 } else {
1073 ui->lineEditPasswordChars->setEnabled(false);
1074 }
1075}
1076
1078
1079void ConfigDialog::onProfileNameEdited(const QString &name) {
1080 ProfileEntry *entry = currentEntry();
1081 if (m_loadingForm || entry == nullptr) {
1082 return;
1083 }
1084 entry->name = name;
1085 if (QListWidgetItem *item = ui->profileList->item(m_currentEntry)) {
1086 item->setText(name);
1087 }
1088 validate();
1090}
1091
1092void ConfigDialog::onProfilePathEdited(const QString &path) {
1093 ProfileEntry *entry = currentEntry();
1094 if (m_loadingForm || entry == nullptr) {
1095 return;
1096 }
1097 entry->profile.path = path;
1098 validate();
1100}
1101
1103 ProfileEntry *entry = currentEntry();
1104 if (m_loadingForm || entry == nullptr) {
1105 return;
1106 }
1107 entry->profile.signingKey = key;
1108 validate();
1109}
1110
1111// A Git box was clicked: the profile now has its own flags.
1113 ProfileEntry *entry = currentEntry();
1114 if (m_loadingForm || entry == nullptr) {
1115 return;
1116 }
1117 entry->profile.useGit = ui->profileUseGit->isChecked();
1118 entry->profile.autoPush = ui->profileAutoPush->isChecked();
1119 entry->profile.autoPull = ui->profileAutoPull->isChecked();
1120 ui->profileAutoPush->setEnabled(ui->profileUseGit->isChecked());
1121 ui->profileAutoPull->setEnabled(ui->profileUseGit->isChecked());
1122}
1123
1125 const ProfileEntry *entry = currentEntry();
1126 if (entry == nullptr) {
1127 ui->statusLabel->setText(QString());
1128 return;
1129 }
1130
1131 QString statusMessage;
1132 if (!entry->name.isEmpty() && !entry->profile.path.isEmpty()) {
1133 const QString path = QDir::cleanPath(entry->profile.path);
1134 if (!QDir(path).exists()) {
1135 statusMessage = tr("New profile: %1 at %2").arg(entry->name, path);
1136 } else {
1137 statusMessage = tr("Profile: %1 at %2").arg(entry->name, path);
1138 }
1139 } else {
1140 statusMessage = tr("Fill in all required fields");
1141 }
1142
1143 ui->statusLabel->setText(statusMessage);
1144}
AppSettings, the value object holding every flat QtPass setting.
void setPasswordConfiguration(const PasswordConfiguration &config)
Apply a password configuration to the dialog controls.
void on_deleteButton_clicked()
Remove the selected profile row (a message when none is); the list's currentRowChanged re-points the ...
std::optional< AppSettings > m_gpgSettings
void on_passwordCharTemplateSelector_activated(int index)
Show the chosen character set's characters in the line edit, editable only for the custom set.
static auto isFingerprintList(const QString &setting) -> bool
Whether a signing-key setting holds only full key fingerprints (40 or 64 hexadecimal characters each,...
void on_toolButtonGit_clicked()
Pick the git executable in a file dialog; a cancelled dialog turns Git off and disables its checkbox,...
auto sharingTheKey(int row, const SigningStores &resolved) const -> QStringList
The other profiles, for other stores, whose signing keys share a fingerprint with row row's.
void validate()
Check every profile (problemsOf(), sharingTheKey()), mark the rows and the form, enable OK only when ...
void updatePassGitWarning()
auto ensureProfileFolder(const QString &path) -> bool
Offer to create a new profile's store folder when it is missing.
void on_radioButtonNative_clicked()
Native (gpg/git) backend chosen: enable its group box, disable pass's.
QList< ProfileEntry > m_entries
The profiles as shown; index == row in profileList.
void useOtp(bool useOtp)
Enable or disable OTP support.
void updateSecretFieldsEnabled()
QString m_nameTip
void loadProfileForm(int row)
Show row row's profile in the form (or clear and disable it for -1), with m_loadingForm set so the fi...
void on_pushButtonGenerateKey_clicked()
Open KeygenDialog for the gpg shown in the dialog.
void setPwgenPath(const QString &path)
Set the path to the pwgen executable.
void on_toolButtonStore_clicked()
Pick the password store folder for the pass backend's store path.
void on_checkBoxUseTrayIcon_clicked()
Hide-on-close and start-minimised are only offered with a tray icon.
QHash< QString, QString > m_primaryOf
void useGit(bool useGit)
Enable or disable git integration.
void on_radioButtonPass_clicked()
pass backend chosen: enable its group box, disable the native one.
void showProblems(int row, const ProfileProblems &problems)
Mark row row of the profile list, and the form fields when it is the row being edited,...
int m_currentEntry
Row whose fields the form shows, -1 for none.
void on_profilePathBrowse_clicked()
Pick the selected profile's store folder and store it in its entry.
QLabel * m_sharedKeyWarning
Below the signing key: the profiles that share it, if any.
QScopedPointer< Ui::ConfigDialog > ui
The widgets from configdialog.ui.
void on_checkBoxUsePwgen_clicked()
Enable pwgen's flags or the character-set controls, whichever the "use pwgen" box selects; nothing in...
auto signerKeys(const QString &signingKey, const QProcessEnvironment &env) const -> QSet< QString >
One profile's signing keys as signers: each key as written, plus the primary fingerprint gpg gives fo...
void setProfiles(Profiles profiles, const QString &currentProfile)
Fill the profile list and m_entries from profiles, dropping the empty-named leftover older versions w...
void useQrencode(bool useQrencode)
Enable or disable QR code display.
void on_autodetectButton_clicked()
Fill the pass, gpg (gpg2 first), git and pwgen paths from PATH, keeping a field as it is when nothing...
auto problemsOf(const ProfileEntry &entry, const QHash< QString, int > &names) const -> ProfileProblems
What is wrong with entry's name, store path and signing keys.
void on_comboBoxClipboard_activated(int index)
Enable the controls that only mean something with a clipboard (selection, hiding, autoclear) unless "...
void onProfilePathEdited(const QString &path)
The path field was typed in: store it in the entry, validate and refresh the status line.
void usePass(bool usePass)
Tick the pass or the native radio button and enable the matching group box.
void useGrepSearch(bool useGrepSearch)
Enable or disable grep content search.
QLabel * m_passGitWarning
In the pass box: the stores pass would commit outside of, if any.
void initializeNewProfiles(const Profiles &existingProfiles)
For every saved profile whose store was not a profile's store when the dialog opened: offer to create...
ConfigDialog(QWidget *parent)
Construct a ConfigDialog associated with the given main window.
void onProfileSelected(int row)
The profile list's current row changed: show that profile.
auto signingStores() const -> SigningStores
Resolve every profile's store and fingerprints once.
auto selectExecutable() -> QString
Ask for an existing file in a read-only file dialog.
QString m_customPasswordChars
void setGitPath(const QString &path)
Show path as the git executable; an empty path disables and turns off "use Git".
void on_toolButtonPass_clicked()
Pick the pass executable in a file dialog; cancel keeps the field.
void setGroupBoxState()
Enable the group box of the chosen backend and, in pass mode, disable the password-generation control...
bool m_recheckSigners
void onProfileGitToggled()
One of the profile's Git boxes was clicked: all three flags become the profile's own (no longer follo...
void on_checkBoxUseGit_clicked()
Enable the Git-dependent boxes (add .gpg-id, auto pull/push, and in native mode the parent-repository...
auto getPasswordConfiguration() -> PasswordConfiguration
Read the current password configuration from the dialog controls.
auto currentEntry() -> ProfileEntry *
The entry the form is editing.
void initialiseProfileStore(const QString &name, const Profile &profile, const QString &path)
Ask for a new profile's recipients and initialise its store with them, keeping the active backend's s...
void criticalMessage(const QString &title, const QString &text)
QMessageBox::critical with a single OK button. Once a workaround for crashes in Qt 5....
void useTrayIcon(bool useSystray)
Show or hide the system tray icon.
void onProfileSigningKeyEdited(const QString &key)
The signing-key field was typed in: store it in the entry and validate (fingerprints only,...
void followActiveProfile(const Profiles &profiles)
Point the active profile at its new name after a rename (or at none after a deletion),...
void on_toolButtonGpg_clicked()
Pick the gpg executable in a file dialog; cancel keeps the field.
void warnAboutSshAuthSockOverride()
void applySettings(const AppSettings &settings)
Load the dialog's value widgets from an AppSettings struct.
auto getProfiles() -> Profiles
Return all configured profiles.
QString m_keyTip
void on_accepted()
On OK: warn about a dubious SSH_AUTH_SOCK override, save the settings and the profiles,...
void on_checkBoxSelection_clicked()
Re-apply the clipboard gating for the current clipboard mode.
void on_toolButtonPwgen_clicked()
Pick the pwgen executable; a choice enables "use pwgen", a cancelled dialog unticks and disables it.
void usePwgen(bool usePwgen)
Enable or disable pwgen-based password generation.
QString m_pathTip
void changeEvent(QEvent *event) override
Forget the signing keys gpg did not know while the dialog is in the background, and check them again ...
void updateProfileStatus()
Refresh the status line under the form: the profile's name and cleaned path, marked "New" while the f...
auto isQrencodeAvailable(const QString &configuredPath) -> bool
Whether qrencode can run: configuredPath when it is an executable file, else a qrencode on Util's PAT...
auto readSettings() -> AppSettings
Build an AppSettings struct from the dialog's widgets.
void onProfileNameEdited(const QString &name)
The name field was typed in: store it in the entry and the list item, then validate (duplicates,...
void on_addButton_clicked()
Append a "New profile" row pointing at the global store path, select it and put the cursor in the nam...
auto selectFolder() -> QString
Ask for a directory in a directories-only file dialog.
void useSelection(bool useSelection)
Enable or disable clipboard selection mode.
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
Handles GPG keypair generation.
static auto needsInit(const QString &path) -> bool
Check if a profile path needs initialization.
static auto gpgEnvironment(const AppSettings &s) -> QProcessEnvironment
The environment QtPass runs gpg in outside the active store's backend: the system's,...
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.
static auto getPass() -> Pass *
Get currently active pass backend instance.
static auto getPasswordConfiguration() -> PasswordConfiguration
Get complete password generation configuration.
static auto load() -> AppSettings
Load all flat settings into an AppSettings value object.
static auto getProfiles() -> Profiles
Get all configured profiles.
static void setQrencodeExecutable(const QString &qrencodeExecutable)
Save qrencode executable path.
static auto getProfile(const QString &defaultValue=QVariant().toString()) -> QString
Get active profile name.
static void setProfiles(const Profiles &profiles)
Save all configured profiles.
static void save(const AppSettings &settings)
Persist an AppSettings value object to the singleton store.
@ Valid
Exists, readable, and on Unix a Unix domain socket.
Definition sshauthsock.h:34
@ NotUnixDomainSocket
Unix only: stat() says the path is not S_ISSOCK.
Definition sshauthsock.h:38
@ DoesNotExist
QFileInfo::exists() returned false.
Definition sshauthsock.h:35
static auto overrideStatus(const QString &path) -> OverrideStatus
Validate an SSH_AUTH_SOCK override path. Caller is expected to skip validation when the input is empt...
Dialog for selecting GPG recipients for password encryption.
Definition usersdialog.h:35
auto selectedUsers() const -> QList< UserInfo >
The key list with the user's choice in each entry's enabled flag.
Definition usersdialog.h:83
@ Nothing
None: for a store that had no list when it was chosen.
Definition usersdialog.h:42
void setInitOnAccept(bool initOnAccept)
Choose whether OK runs Pass::Init on the folder.
Definition usersdialog.h:77
static auto repositoryPassClimbsTo(const QString &store) -> QString
The repository above store that pass itself would commit to, if any.
Definition util.cpp:146
static auto configIsValid(const AppSettings &s) -> bool
Verify that the required configuration is complete.
Definition util.cpp:556
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
Settings dialog: every page of the configuration window and the profile table, with the validation th...
Executor, the FIFO queue every gpg, git and pass child process of QtPass goes through,...
auto primaryFingerprintOf(const QString &output, const QString &key) -> QString
The primary key fingerprint of the key that key names.
Parsing of gpg --with-colons key listings into UserInfo records.
Dialog that collects name, e-mail and passphrase for a new GPG keypair and drives gpg --gen-key --bat...
ClipBoardType
Defines when to copy passwords to clipboard.
Definition enums.h:24
void attach(QDialog &dialog, const QString &key)
restore() now, save() whenever the dialog finishes.
QMap< QString, Profile > Profiles
All profiles by name. QMap so iteration order is the name order.
Definition profile.h:81
Initialising a new profile's store (.gpg-id, signature, Git) with blocking processes,...
The "qtpass" logging category.
QtPassSettings, the QSettings singleton behind every saved setting, the profiles and the Pass backend...
SshAuthSock: finding an SSH agent socket for child processes and judging a configured override.
Plain value-object holding all flat QtPass configuration.
Definition appsettings.h:36
bool displayAsIs
Show file content unmodified.
Definition appsettings.h:97
bool useGrepSearch
Enable content (grep) search.
QString gpgExecutable
Path to the gpg/gpg2 executable.
Definition appsettings.h:66
QString qrencodeExecutable
Path to the qrencode executable.
Definition appsettings.h:68
QString sshAuthSockOverride
Manual SSH_AUTH_SOCK override path.
Definition appsettings.h:70
bool lessRandom
Generate memorable (less random) passwords.
bool useSymbols
Include special symbols.
bool hideOnClose
Hide to tray instead of quitting on close.
QStringList secretFieldNames
Definition appsettings.h:91
QString passSigningKey
GPG key used to sign .gpg-id files.
Definition appsettings.h:61
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
bool hideContent
Hide the entire content panel.
Definition appsettings.h:95
bool useOtp
Enable native TOTP (OTP) support.
QString passStore
Password-store root path.
Definition appsettings.h:60
bool hidePassword
Definition appsettings.h:85
bool useAutoclearPanel
Clear the content panel after a delay.
Definition appsettings.h:81
bool noLineWrapping
Disable line wrapping in the panel.
Definition appsettings.h:98
QString gitExecutable
Path to the git executable.
Definition appsettings.h:65
bool autoPull
Automatically git pull on open.
bool alwaysOnTop
Keep the main window always on top.
int autoclearPanelSeconds
Panel autoclear delay (seconds).
Definition appsettings.h:82
bool hideSecretFields
Definition appsettings.h:88
bool startMinimized
Start the window minimised.
bool usePwgen
Use pwgen for password generation.
bool useTrayIcon
Show a system tray icon.
bool gitUseParentRepository
QString passExecutable
Path to the pass executable.
Definition appsettings.h:64
bool avoidCapitals
Exclude uppercase letters.
int autoclearSeconds
Clipboard autoclear delay (seconds).
Definition appsettings.h:80
bool useSelection
Use the X11 primary selection.
Definition appsettings.h:75
bool useGit
Enable git integration.
bool restoreClipboard
Definition appsettings.h:79
bool addGPGId
Auto-add .gpg-id files to git.
Enums::ClipBoardType clipBoardType
Clipboard copy behaviour.
Definition appsettings.h:73
bool hideAllFields
Definition appsettings.h:94
QString version
Last-run application version.
Definition appsettings.h:53
QString passTemplate
Newline-separated template field names.
bool useAutoclear
Definition appsettings.h:76
bool useMonospace
Render content in a monospace font.
Definition appsettings.h:96
bool templateAllFields
Treat every key: line as a field.
bool avoidNumbers
Exclude digits.
bool useQrencode
Enable qrencode support.
QString activeProfile
Name of the active profile.
Definition appsettings.h:56
bool useTemplate
Use the password template.
PasswordConfiguration passwordConfiguration
Length/charset/custom chars.
One row of the profile list while the dialog is open. A QList rather than the Profiles map so a profi...
QString name
The name as typed; may be empty or a duplicate.
Profile profile
What is wrong with one profile's fields; empty strings where nothing is.
QString name
Empty or duplicate name; empty when fine.
auto none() const -> bool
Whether all three fields are fine.
auto first() const -> QString
The first complaint, for a tooltip on the profile's row.
QString path
Missing store path; empty when fine.
QStringList stores
Canonical (or cleaned) store path.
QList< QSet< QString > > keys
Fingerprints of the signing key.
Holds the password generator settings: which character set is selected, the alphabet of every set,...
int length
Length of the generated password, in characters.
CharacterSet selected
Currently active character set selection.
QString Characters[CHARSETS_COUNT]
The alphabet of each character set, indexed by CharacterSet. Only the CUSTOM entry is meant to be cha...
CharacterSet
Character set options for password generation; also the index into Characters.
One named password store: where it is, how its .gpg-id is signed and, optionally, its own Git switche...
Definition profile.h:26
std::optional< bool > autoPush
Push after commits; unset = global.
Definition profile.h:30
QString path
Password store directory.
Definition profile.h:27
std::optional< bool > autoPull
Pull on start; unset = global.
Definition profile.h:31
std::optional< bool > useGit
Git on/off; unset follows the global.
Definition profile.h:29
QString signingKey
Key(s) that sign this store's .gpg-id.
Definition profile.h:28
UsersDialog, the recipient picker for a folder's .gpg-id.
Util: static helpers for the process environment, binary discovery, store paths, URL safety and the l...
WindowStateStore: saves and restores top-level window geometry through QtPassSettings,...