Line data Source code
1 : // SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
2 : // SPDX-License-Identifier: GPL-3.0-or-later
3 : /**
4 : * @file
5 : * @brief The first-run wizard and its four pages: programs, key, store and
6 : * summary.
7 : */
8 : #ifndef SRC_FIRSTRUNWIZARD_H_
9 : #define SRC_FIRSTRUNWIZARD_H_
10 :
11 : #include "appsettings.h"
12 : #include "userinfo.h"
13 : #include <QList>
14 : #include <QString>
15 : #include <QWizard>
16 : #include <QWizardPage>
17 :
18 : class QCheckBox;
19 : class QLabel;
20 : class QLineEdit;
21 : class QListWidget;
22 : class QPushButton;
23 :
24 : /**
25 : * @class FirstRunWizard
26 : * @ingroup ui
27 : * @brief The first start of QtPass: find GnuPG (and pass and Git), make sure
28 : * there is a secret key, pick or create the password store. Replaces the
29 : * chain of message boxes ConfigDialog::wizard() used to run before showing
30 : * the whole configuration dialog.
31 : *
32 : * The wizard works on a copy of the settings and writes it back on Finish,
33 : * after it has created and initialised the store when that was needed
34 : * (ProfileInit). Cancel leaves the settings as they were; the caller keeps
35 : * asking until the configuration is usable or the user gives up.
36 : */
37 : class FirstRunWizard : public QWizard {
38 167 : Q_OBJECT
39 :
40 : public:
41 : /**
42 : * @brief Build the wizard from the current settings.
43 : * @param parent The main window.
44 : */
45 : explicit FirstRunWizard(QWidget *parent = nullptr);
46 :
47 : /**
48 : * @brief The settings as the pages have edited them so far.
49 : * @return A copy.
50 : */
51 : auto settings() const -> AppSettings { return m_settings; }
52 :
53 : /**
54 : * @brief The secret keys gpg lists for @p gpgExecutable.
55 : * @param gpgExecutable The gpg binary.
56 : * @return The keys, or an empty list when gpg cannot run.
57 : */
58 : static auto secretKeys(const QString &gpgExecutable) -> QList<UserInfo>;
59 :
60 : /**
61 : * @brief Whether @p path points at an initialised store.
62 : * @param path A directory.
63 : * @return true when it holds a `.gpg-id`.
64 : */
65 : static auto isStore(const QString &path) -> bool;
66 :
67 : /// Page ids, in order.
68 : enum Page {
69 : IntroPage, ///< What QtPass is and what the wizard will do.
70 : ProgramsPage, ///< ProgramsWizardPage: where gpg, pass and git are.
71 : KeyPage, ///< KeyWizardPage: the secret keys, with generation.
72 : StorePage, ///< StoreWizardPage: the store folder and the Git choice.
73 : DonePage ///< DoneWizardPage: summary and two preferences.
74 : };
75 :
76 : /**
77 : * @brief Write the settings and, when the chosen store has no `.gpg-id`
78 : * yet, create and initialise it for the chosen keys first.
79 : */
80 : void accept() override;
81 :
82 : private:
83 : /**
84 : * @brief Make @p store a password store for the ticked keys: create the
85 : * folder when it is missing, write and sign its `.gpg-id`, and set up Git
86 : * when chosen. On failure what was written is taken back, so a second
87 : * Finish runs the whole initialisation again.
88 : * @param store The folder, cleaned.
89 : * @return Whether the store is ready (the user was told otherwise).
90 : */
91 : auto initialiseStore(const QString &store) -> bool;
92 : /**
93 : * @brief Give an existing store the Git repository it was chosen to have.
94 : * @param store The folder, cleaned.
95 : * @return Whether the repository is ready (the user was told otherwise).
96 : */
97 : auto initialiseGit(const QString &store) -> bool;
98 :
99 : friend class ProgramsWizardPage;
100 : friend class KeyWizardPage;
101 : friend class StoreWizardPage;
102 : friend class DoneWizardPage;
103 :
104 : /// The pages' working copy of the settings, loaded in the constructor and
105 : /// saved by accept(); the pages edit it through their friend access.
106 : AppSettings m_settings;
107 : /// The secret keys as listed on the key page; the enabled ones become the
108 : /// recipients of a new store.
109 : QList<UserInfo> m_keys;
110 : };
111 :
112 : /**
113 : * @class ProgramsWizardPage
114 : * @ingroup ui
115 : * @brief Where gpg, pass and git are; gpg is required, the rest optional. A
116 : * stored path that no longer runs is replaced by what PATH has.
117 : */
118 : class ProgramsWizardPage : public QWizardPage {
119 166 : Q_OBJECT
120 :
121 : public:
122 : /**
123 : * @brief Build the page.
124 : * @param wizard Owner.
125 : */
126 : explicit ProgramsWizardPage(FirstRunWizard *wizard);
127 : /**
128 : * @brief Fill the fields from the settings (autodetection already ran).
129 : */
130 : void initializePage() override;
131 : /**
132 : * @brief Whether the gpg path points at something that can run.
133 : * @return true when gpg exists (or is a `wsl` command).
134 : */
135 : auto isComplete() const -> bool override;
136 : /**
137 : * @brief Store the paths and the pass/Git choices in the settings.
138 : * @return Always true; isComplete() gates Next.
139 : */
140 : auto validatePage() -> bool override;
141 :
142 : private:
143 : FirstRunWizard *m_wizard; ///< Owner; its settings are what the page edits.
144 : QLineEdit *m_gpg; ///< The gpg executable; required.
145 : QLineEdit *m_git; ///< The git executable; optional.
146 : QLineEdit *m_pass; ///< The pass executable; optional.
147 : QCheckBox *m_usePass; ///< Run pass instead of gpg/git; needs m_pass.
148 : QLabel *m_gpgStatus; ///< Under the gpg field: why it cannot be used.
149 :
150 : /**
151 : * @brief After an edit: say under the gpg field when it is missing or not
152 : * executable, allow "use pass" only for a runnable pass (unticking it
153 : * otherwise), and re-evaluate isComplete().
154 : */
155 : void updateStatus();
156 : };
157 :
158 : /**
159 : * @class KeyWizardPage
160 : * @ingroup ui
161 : * @brief The secret keys gpg knows; a new pair can be generated here. The
162 : * enabled ones become the recipients of a store that still has to be
163 : * initialised.
164 : */
165 : class KeyWizardPage : public QWizardPage {
166 72 : Q_OBJECT
167 :
168 : public:
169 : /**
170 : * @brief Build the page.
171 : * @param wizard Owner.
172 : */
173 : explicit KeyWizardPage(FirstRunWizard *wizard);
174 : /**
175 : * @brief List the secret keys for the gpg chosen on the previous page.
176 : */
177 : void initializePage() override;
178 : /**
179 : * @brief Always passable: an existing store needs no recipient choice
180 : * here (and its key may not be imported yet); the store page insists on a
181 : * ticked key when it has to initialise.
182 : * @return true.
183 : */
184 : auto isComplete() const -> bool override;
185 : /**
186 : * @brief Remember which keys are enabled.
187 : * @return Always true.
188 : */
189 : auto validatePage() -> bool override;
190 :
191 : private:
192 : FirstRunWizard *m_wizard; ///< Owner; holds the key list this page fills.
193 : QListWidget *m_list; ///< One checkable row per secret key.
194 : QPushButton *m_generate; ///< Opens KeygenDialog.
195 : QLabel *m_status; ///< Says so when gpg has no secret key yet.
196 :
197 : /**
198 : * @brief List the secret keys of the chosen gpg into the wizard and the
199 : * widget, ticking every usable key and greying out expired or invalid
200 : * ones.
201 : */
202 : void reload();
203 : /**
204 : * @brief Run KeygenDialog on a private ImitatePass built from the wizard's
205 : * settings, so the chosen gpg is used and nothing is saved, then reload().
206 : */
207 : void generate();
208 : };
209 :
210 : /**
211 : * @class StoreWizardPage
212 : * @ingroup ui
213 : * @brief The folder the passwords live in: an existing store is used as-is,
214 : * a folder without `.gpg-id` (or no folder) is initialised on Finish. The
215 : * Git box lives here because its sensible default depends on the folder: on
216 : * for a repository or a store still to be created, off for an existing
217 : * store that is no repository.
218 : */
219 : class StoreWizardPage : public QWizardPage {
220 143 : Q_OBJECT
221 :
222 : public:
223 : /**
224 : * @brief Build the page.
225 : * @param wizard Owner.
226 : */
227 : explicit StoreWizardPage(FirstRunWizard *wizard);
228 : /**
229 : * @brief Show the configured store path.
230 : */
231 : void initializePage() override;
232 : /**
233 : * @brief Whether a path is given and, for a folder that is not a store
234 : * yet, at least one key was ticked to encrypt it to.
235 : * @return true when Finish can do its job.
236 : */
237 : auto isComplete() const -> bool override;
238 : /**
239 : * @brief Store the path and the Git choice.
240 : * @return Always true.
241 : */
242 : auto validatePage() -> bool override;
243 :
244 : private:
245 : FirstRunWizard *m_wizard; ///< Owner; its settings get the path and flag.
246 : QLineEdit *m_path; ///< The store folder, in native separators.
247 : QCheckBox *m_useGit; ///< Keep the store under Git; off without git.
248 : QLabel *m_status; ///< What Finish will do with the folder.
249 :
250 : /**
251 : * @brief How many keys are ticked on the key page.
252 : * @return The number of enabled entries in the wizard's key list.
253 : */
254 : auto recipients() const -> int;
255 : /**
256 : * @brief After an edit: describe the folder (existing store with its
257 : * entry count, empty folder, not a store yet, missing) and ask for a
258 : * ticked key when it has to be initialised, then re-evaluate isComplete().
259 : */
260 : void updateStatus();
261 : };
262 :
263 : /**
264 : * @class DoneWizardPage
265 : * @ingroup ui
266 : * @brief Last page: a summary of what Finish will do and the two
267 : * preferences worth asking on day one.
268 : *
269 : * The summary names the store and says whether it is used as it is or set
270 : * up for the ticked keys, with or without Git. The two checkboxes are
271 : * "hide passwords until asked" and "show an icon in the system tray";
272 : * everything else is left to the settings dialog.
273 : */
274 : class DoneWizardPage : public QWizardPage {
275 151 : Q_OBJECT
276 :
277 : public:
278 : /**
279 : * @brief Build the page.
280 : * @param wizard Owner.
281 : */
282 : explicit DoneWizardPage(FirstRunWizard *wizard);
283 : /**
284 : * @brief Fill the summary from the settings.
285 : */
286 : void initializePage() override;
287 : /**
288 : * @brief Store the preferences.
289 : * @return Always true.
290 : */
291 : auto validatePage() -> bool override;
292 :
293 : private:
294 : FirstRunWizard *m_wizard; ///< Owner; its settings get the preferences.
295 : QLabel *m_summary; ///< Store, what Finish does with it, gpg, mode.
296 : QCheckBox *m_hidePassword; ///< Show the password line as dots; on.
297 : QCheckBox *m_trayIcon; ///< Tray icon; hidden when there is no tray.
298 : };
299 :
300 : #endif // SRC_FIRSTRUNWIZARD_H_
|