keepassxc

Форк
0
/
MainWindow.cpp 
2246 строк · 94.0 Кб
1
/*
2
 *  Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
3
 *  Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU General Public License as published by
7
 *  the Free Software Foundation, either version 2 or (at your option)
8
 *  version 3 of the License.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU General Public License
16
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18

19
#include "MainWindow.h"
20
#include "ui_MainWindow.h"
21

22
#include <QCloseEvent>
23
#include <QDesktopServices>
24
#include <QDir>
25
#include <QFileInfo>
26
#include <QList>
27
#include <QMimeData>
28
#include <QShortcut>
29
#include <QStatusBar>
30
#include <QTimer>
31
#include <QToolButton>
32
#include <QWindow>
33

34
#include "config-keepassx.h"
35

36
#include "Application.h"
37
#include "Clipboard.h"
38
#include "autotype/AutoType.h"
39
#include "core/InactivityTimer.h"
40
#include "core/Resources.h"
41
#include "core/Tools.h"
42
#include "gui/AboutDialog.h"
43
#include "gui/ActionCollection.h"
44
#include "gui/Icons.h"
45
#include "gui/MessageBox.h"
46
#include "gui/SearchWidget.h"
47
#include "gui/ShortcutSettingsPage.h"
48
#include "gui/entry/EntryView.h"
49
#include "gui/osutils/OSUtils.h"
50

51
#ifdef WITH_XC_UPDATECHECK
52
#include "gui/UpdateCheckDialog.h"
53
#include "updatecheck/UpdateChecker.h"
54
#endif
55

56
#ifdef WITH_XC_SSHAGENT
57
#include "sshagent/AgentSettingsPage.h"
58
#include "sshagent/SSHAgent.h"
59
#endif
60
#ifdef WITH_XC_KEESHARE
61
#include "keeshare/KeeShare.h"
62
#include "keeshare/SettingsPageKeeShare.h"
63
#endif
64

65
#ifdef WITH_XC_FDOSECRETS
66
#include "fdosecrets/FdoSecretsPlugin.h"
67
#endif
68

69
#ifdef WITH_XC_YUBIKEY
70
#include "keys/drivers/YubiKey.h"
71
#endif
72

73
#ifdef WITH_XC_BROWSER
74
#include "browser/BrowserService.h"
75
#include "browser/BrowserSettingsPage.h"
76
#endif
77

78
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && !defined(QT_NO_DBUS)
79
#include "mainwindowadaptor.h"
80
#endif
81

82
const QString MainWindow::BaseWindowTitle = "KeePassXC";
83

84
MainWindow* g_MainWindow = nullptr;
85
MainWindow* getMainWindow()
86
{
87
    return g_MainWindow;
88
}
89

90
MainWindow::MainWindow()
91
    : m_ui(new Ui::MainWindow())
92
{
93
    g_MainWindow = this;
94

95
    m_ui->setupUi(this);
96

97
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && !defined(QT_NO_DBUS)
98
    new MainWindowAdaptor(this);
99
    QDBusConnection dbus = QDBusConnection::sessionBus();
100
    dbus.registerObject("/keepassxc", this);
101
    dbus.registerService("org.keepassxc.KeePassXC.MainWindow");
102
#endif
103

104
    setAcceptDrops(true);
105

106
    if (config()->get(Config::GUI_CompactMode).toBool()) {
107
        m_ui->toolBar->setIconSize({20, 20});
108
    }
109

110
    // Setup the search widget in the toolbar
111
    m_searchWidget = new SearchWidget();
112
    m_searchWidget->connectSignals(m_actionMultiplexer);
113
    m_searchWidgetAction = m_ui->toolBar->addWidget(m_searchWidget);
114
    m_searchWidgetAction->setEnabled(false);
115

116
    new QShortcut(QKeySequence::Find, this, SLOT(focusSearchWidget()));
117

118
    connect(m_searchWidget, &SearchWidget::searchCanceled, this, [this] {
119
        m_ui->toolBar->setExpanded(false);
120
        m_ui->toolBar->setVisible(!config()->get(Config::GUI_HideToolbar).toBool());
121
    });
122
    connect(m_searchWidget, &SearchWidget::lostFocus, this, [this] {
123
        m_ui->toolBar->setExpanded(false);
124
        m_ui->toolBar->setVisible(!config()->get(Config::GUI_HideToolbar).toBool());
125
    });
126

127
    m_countDefaultAttributes = m_ui->menuEntryCopyAttribute->actions().size();
128

129
    m_entryContextMenu = new QMenu(this);
130
    m_entryContextMenu->setSeparatorsCollapsible(true);
131
    m_entryContextMenu->addAction(m_ui->actionEntryCopyUsername);
132
    m_entryContextMenu->addAction(m_ui->actionEntryCopyPassword);
133
    m_entryContextMenu->addAction(m_ui->actionEntryCopyURL);
134
    m_entryContextMenu->addAction(m_ui->menuEntryCopyAttribute->menuAction());
135
    m_entryContextMenu->addAction(m_ui->menuEntryTotp->menuAction());
136
    m_entryContextMenu->addAction(m_ui->menuTags->menuAction());
137
    m_entryContextMenu->addSeparator();
138
    m_entryContextMenu->addAction(m_ui->actionEntryAutoType);
139
    m_entryContextMenu->addSeparator();
140
#ifdef WITH_XC_BROWSER_PASSKEYS
141
    m_entryContextMenu->addAction(m_ui->actionEntryImportPasskey);
142
    m_entryContextMenu->addSeparator();
143
#endif
144
    m_entryContextMenu->addAction(m_ui->actionEntryEdit);
145
    m_entryContextMenu->addAction(m_ui->actionEntryClone);
146
    m_entryContextMenu->addAction(m_ui->actionEntryDelete);
147
    m_entryContextMenu->addAction(m_ui->actionEntryNew);
148
    m_entryContextMenu->addSeparator();
149
    m_entryContextMenu->addAction(m_ui->actionEntryMoveUp);
150
    m_entryContextMenu->addAction(m_ui->actionEntryMoveDown);
151
    m_entryContextMenu->addSeparator();
152
    m_entryContextMenu->addAction(m_ui->actionEntryOpenUrl);
153
    m_entryContextMenu->addAction(m_ui->actionEntryDownloadIcon);
154
    m_entryContextMenu->addSeparator();
155
    m_entryContextMenu->addAction(m_ui->actionEntryAddToAgent);
156
    m_entryContextMenu->addAction(m_ui->actionEntryRemoveFromAgent);
157
    m_entryContextMenu->addSeparator();
158
    m_entryContextMenu->addAction(m_ui->actionEntryRestore);
159

160
    m_entryNewContextMenu = new QMenu(this);
161
    m_entryNewContextMenu->addAction(m_ui->actionEntryNew);
162

163
    // Build Entry Level Auto-Type menu
164
    auto autotypeMenu = new QMenu({}, this);
165
    autotypeMenu->addAction(m_ui->actionEntryAutoTypeSequence);
166
    autotypeMenu->addSeparator();
167
    autotypeMenu->addAction(m_ui->actionEntryAutoTypeUsername);
168
    autotypeMenu->addAction(m_ui->actionEntryAutoTypeUsernameEnter);
169
    autotypeMenu->addAction(m_ui->actionEntryAutoTypePassword);
170
    autotypeMenu->addAction(m_ui->actionEntryAutoTypePasswordEnter);
171
    autotypeMenu->addAction(m_ui->actionEntryAutoTypeTOTP);
172
    m_ui->actionEntryAutoType->setMenu(autotypeMenu);
173
    auto autoTypeButton = qobject_cast<QToolButton*>(m_ui->toolBar->widgetForAction(m_ui->actionEntryAutoType));
174
    if (autoTypeButton) {
175
        autoTypeButton->setPopupMode(QToolButton::MenuButtonPopup);
176
    }
177

178
    auto databaseLockMenu = new QMenu({}, this);
179
    databaseLockMenu->addAction(m_ui->actionLockAllDatabases);
180
    m_ui->actionLockDatabaseToolbar->setMenu(databaseLockMenu);
181
    auto databaseLockButton =
182
        qobject_cast<QToolButton*>(m_ui->toolBar->widgetForAction(m_ui->actionLockDatabaseToolbar));
183
    if (databaseLockButton) {
184
        databaseLockButton->setPopupMode(QToolButton::MenuButtonPopup);
185
    }
186

187
    restoreGeometry(config()->get(Config::GUI_MainWindowGeometry).toByteArray());
188
    restoreState(config()->get(Config::GUI_MainWindowState).toByteArray());
189

190
    connect(m_ui->tabWidget, &DatabaseTabWidget::databaseLocked, this, &MainWindow::databaseLocked);
191
    connect(m_ui->tabWidget, &DatabaseTabWidget::databaseUnlocked, this, &MainWindow::databaseUnlocked);
192
    connect(m_ui->tabWidget, &DatabaseTabWidget::activeDatabaseChanged, this, &MainWindow::activeDatabaseChanged);
193

194
    initViewMenu();
195
    initActionCollection();
196

197
    m_ui->settingsWidget->addSettingsPage(new ShortcutSettingsPage());
198

199
#ifdef WITH_XC_BROWSER
200
    m_ui->settingsWidget->addSettingsPage(new BrowserSettingsPage());
201
    connect(
202
        browserService(), &BrowserService::requestUnlock, m_ui->tabWidget, &DatabaseTabWidget::performBrowserUnlock);
203
#endif
204

205
#ifdef WITH_XC_SSHAGENT
206
    connect(sshAgent(), SIGNAL(error(QString)), this, SLOT(showErrorMessage(QString)));
207
    connect(sshAgent(), SIGNAL(enabledChanged(bool)), this, SLOT(agentEnabled(bool)));
208
    m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage());
209
#endif
210

211
#if defined(WITH_XC_KEESHARE)
212
    KeeShare::init(this);
213
    m_ui->settingsWidget->addSettingsPage(new SettingsPageKeeShare(m_ui->tabWidget));
214
    connect(KeeShare::instance(),
215
            SIGNAL(sharingMessage(QString, MessageWidget::MessageType)),
216
            SLOT(displayGlobalMessage(QString, MessageWidget::MessageType)));
217
#endif
218

219
#ifdef WITH_XC_FDOSECRETS
220
    auto fdoSS = new FdoSecretsPlugin(m_ui->tabWidget);
221
    connect(fdoSS, &FdoSecretsPlugin::error, this, &MainWindow::showErrorMessage);
222
    connect(fdoSS, &FdoSecretsPlugin::requestSwitchToDatabases, this, &MainWindow::switchToDatabases);
223
    connect(fdoSS, &FdoSecretsPlugin::requestShowNotification, this, &MainWindow::displayDesktopNotification);
224
    fdoSS->updateServiceState();
225
    m_ui->settingsWidget->addSettingsPage(fdoSS);
226
#endif
227

228
#ifdef WITH_XC_YUBIKEY
229
    connect(YubiKey::instance(), SIGNAL(userInteractionRequest()), SLOT(showYubiKeyPopup()), Qt::QueuedConnection);
230
    connect(YubiKey::instance(), SIGNAL(challengeCompleted()), SLOT(hideYubiKeyPopup()), Qt::QueuedConnection);
231
#endif
232

233
    setWindowIcon(icons()->applicationIcon());
234
    m_ui->globalMessageWidget->hideMessage();
235
    connect(m_ui->globalMessageWidget, &MessageWidget::linkActivated, &MessageWidget::openHttpUrl);
236

237
    m_clearHistoryAction = new QAction(tr("Clear history"), m_ui->menuFile);
238
    m_lastDatabasesActions = new QActionGroup(m_ui->menuRecentDatabases);
239
    connect(m_clearHistoryAction, SIGNAL(triggered()), this, SLOT(clearLastDatabases()));
240
    connect(m_lastDatabasesActions, SIGNAL(triggered(QAction*)), this, SLOT(openRecentDatabase(QAction*)));
241
    connect(m_ui->menuRecentDatabases, SIGNAL(aboutToShow()), this, SLOT(updateLastDatabasesMenu()));
242

243
    m_copyAdditionalAttributeActions = new QActionGroup(m_ui->menuEntryCopyAttribute);
244
    m_actionMultiplexer.connect(
245
        m_copyAdditionalAttributeActions, SIGNAL(triggered(QAction*)), SLOT(copyAttribute(QAction*)));
246
    connect(m_ui->menuEntryCopyAttribute, SIGNAL(aboutToShow()), this, SLOT(updateCopyAttributesMenu()));
247

248
    m_setTagsMenuActions = new QActionGroup(m_ui->menuTags);
249
    m_setTagsMenuActions->setExclusive(false);
250
    m_actionMultiplexer.connect(m_setTagsMenuActions, SIGNAL(triggered(QAction*)), SLOT(setTag(QAction*)));
251
    connect(m_ui->menuTags, &QMenu::aboutToShow, this, &MainWindow::updateSetTagsMenu);
252

253
    Qt::Key globalAutoTypeKey = static_cast<Qt::Key>(config()->get(Config::GlobalAutoTypeKey).toInt());
254
    Qt::KeyboardModifiers globalAutoTypeModifiers =
255
        static_cast<Qt::KeyboardModifiers>(config()->get(Config::GlobalAutoTypeModifiers).toInt());
256
    if (globalAutoTypeKey > 0 && globalAutoTypeModifiers > 0) {
257
        autoType()->registerGlobalShortcut(globalAutoTypeKey, globalAutoTypeModifiers);
258
    }
259

260
    m_ui->toolbarSeparator->setVisible(false);
261
    m_showToolbarSeparator = config()->get(Config::GUI_ApplicationTheme).toString() != "classic";
262

263
    m_ui->actionEntryAutoType->setVisible(autoType()->isAvailable());
264
    m_ui->actionAllowScreenCapture->setVisible(osUtils->canPreventScreenCapture());
265

266
    m_inactivityTimer = new InactivityTimer(this);
267
    connect(m_inactivityTimer, SIGNAL(inactivityDetected()), this, SLOT(lockDatabasesAfterInactivity()));
268
    applySettingsChanges();
269

270
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
271
    // Qt 5.10 introduced a new "feature" to hide shortcuts in context menus
272
    // Unfortunately, Qt::AA_DontShowShortcutsInContextMenus is broken, have to manually enable them
273
    m_ui->actionEntryNew->setShortcutVisibleInContextMenu(true);
274
    m_ui->actionEntryEdit->setShortcutVisibleInContextMenu(true);
275
    m_ui->actionEntryDelete->setShortcutVisibleInContextMenu(true);
276
    m_ui->actionEntryRestore->setShortcutVisibleInContextMenu(true);
277
    m_ui->actionEntryClone->setShortcutVisibleInContextMenu(true);
278
    m_ui->actionEntryTotp->setShortcutVisibleInContextMenu(true);
279
    m_ui->actionEntryDownloadIcon->setShortcutVisibleInContextMenu(true);
280
    m_ui->actionEntryCopyTotp->setShortcutVisibleInContextMenu(true);
281
    m_ui->actionEntryCopyPasswordTotp->setShortcutVisibleInContextMenu(true);
282
    m_ui->actionEntryMoveUp->setShortcutVisibleInContextMenu(true);
283
    m_ui->actionEntryMoveDown->setShortcutVisibleInContextMenu(true);
284
    m_ui->actionEntryCopyUsername->setShortcutVisibleInContextMenu(true);
285
    m_ui->actionEntryCopyPassword->setShortcutVisibleInContextMenu(true);
286
    m_ui->actionEntryAutoTypeSequence->setShortcutVisibleInContextMenu(true);
287
    m_ui->actionEntryOpenUrl->setShortcutVisibleInContextMenu(true);
288
    m_ui->actionEntryCopyURL->setShortcutVisibleInContextMenu(true);
289
    m_ui->actionEntryCopyTitle->setShortcutVisibleInContextMenu(true);
290
    m_ui->actionEntryAddToAgent->setShortcutVisibleInContextMenu(true);
291
    m_ui->actionEntryRemoveFromAgent->setShortcutVisibleInContextMenu(true);
292
#endif
293

294
    connect(m_ui->menuEntries, SIGNAL(aboutToShow()), SLOT(obtainContextFocusLock()));
295
    connect(m_ui->menuEntries, SIGNAL(aboutToHide()), SLOT(releaseContextFocusLock()));
296
    connect(m_entryContextMenu, SIGNAL(aboutToShow()), SLOT(obtainContextFocusLock()));
297
    connect(m_entryContextMenu, SIGNAL(aboutToHide()), SLOT(releaseContextFocusLock()));
298
    connect(m_entryNewContextMenu, SIGNAL(aboutToShow()), SLOT(obtainContextFocusLock()));
299
    connect(m_entryNewContextMenu, SIGNAL(aboutToHide()), SLOT(releaseContextFocusLock()));
300
    connect(m_ui->menuGroups, SIGNAL(aboutToShow()), SLOT(obtainContextFocusLock()));
301
    connect(m_ui->menuGroups, SIGNAL(aboutToHide()), SLOT(releaseContextFocusLock()));
302

303
    // Control window state
304
    new QShortcut(Qt::CTRL + Qt::Key_M, this, SLOT(minimizeOrHide()));
305
    new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_M, this, SLOT(hideWindow()));
306
    // Control database tabs
307
    // Ctrl+Tab is broken on Mac, so use Alt (i.e. the Option key) - https://bugreports.qt.io/browse/QTBUG-8596
308
    auto dbTabModifier2 = Qt::CTRL;
309
#ifdef Q_OS_MACOS
310
    dbTabModifier2 = Qt::ALT;
311
#endif
312
    new QShortcut(dbTabModifier2 + Qt::Key_Tab, this, SLOT(selectNextDatabaseTab()));
313
    new QShortcut(Qt::CTRL + Qt::Key_PageDown, this, SLOT(selectNextDatabaseTab()));
314
    new QShortcut(dbTabModifier2 + Qt::SHIFT + Qt::Key_Tab, this, SLOT(selectPreviousDatabaseTab()));
315
    new QShortcut(Qt::CTRL + Qt::Key_PageUp, this, SLOT(selectPreviousDatabaseTab()));
316

317
    // Tab selection by number, Windows uses Ctrl, macOS uses Command,
318
    // and Linux uses Alt to emulate a browser-like experience
319
    auto dbTabModifier = Qt::CTRL;
320
#ifdef Q_OS_LINUX
321
    dbTabModifier = Qt::ALT;
322
#endif
323
    auto shortcut = new QShortcut(dbTabModifier + Qt::Key_1, this);
324
    connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(0); });
325
    shortcut = new QShortcut(dbTabModifier + Qt::Key_2, this);
326
    connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(1); });
327
    shortcut = new QShortcut(dbTabModifier + Qt::Key_3, this);
328
    connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(2); });
329
    shortcut = new QShortcut(dbTabModifier + Qt::Key_4, this);
330
    connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(3); });
331
    shortcut = new QShortcut(dbTabModifier + Qt::Key_5, this);
332
    connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(4); });
333
    shortcut = new QShortcut(dbTabModifier + Qt::Key_6, this);
334
    connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(5); });
335
    shortcut = new QShortcut(dbTabModifier + Qt::Key_7, this);
336
    connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(6); });
337
    shortcut = new QShortcut(dbTabModifier + Qt::Key_8, this);
338
    connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(7); });
339
    shortcut = new QShortcut(dbTabModifier + Qt::Key_9, this);
340
    connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(m_ui->tabWidget->count() - 1); });
341

342
    m_ui->actionDatabaseNew->setIcon(icons()->icon("document-new"));
343
    m_ui->actionDatabaseOpen->setIcon(icons()->icon("document-open"));
344
    m_ui->menuRecentDatabases->setIcon(icons()->icon("document-open-recent"));
345
    m_ui->actionDatabaseSave->setIcon(icons()->icon("document-save"));
346
    m_ui->actionDatabaseSaveAs->setIcon(icons()->icon("document-save-as"));
347
    m_ui->actionDatabaseSaveBackup->setIcon(icons()->icon("document-save-copy"));
348
    m_ui->actionDatabaseClose->setIcon(icons()->icon("document-close"));
349
    m_ui->actionReports->setIcon(icons()->icon("reports"));
350
    m_ui->actionDatabaseSettings->setIcon(icons()->icon("document-edit"));
351
    m_ui->actionDatabaseSecurity->setIcon(icons()->icon("database-change-key"));
352
    m_ui->actionLockDatabase->setIcon(icons()->icon("database-lock"));
353
    m_ui->actionLockDatabaseToolbar->setIcon(icons()->icon("database-lock"));
354
    m_ui->actionLockAllDatabases->setIcon(icons()->icon("database-lock-all"));
355
    m_ui->actionQuit->setIcon(icons()->icon("application-exit"));
356
    m_ui->actionDatabaseMerge->setIcon(icons()->icon("database-merge"));
357
    m_ui->actionImport->setIcon(icons()->icon("document-import"));
358
    m_ui->menuExport->setIcon(icons()->icon("document-export"));
359

360
    m_ui->actionEntryNew->setIcon(icons()->icon("entry-new"));
361
    m_ui->actionEntryClone->setIcon(icons()->icon("entry-clone"));
362
    m_ui->actionEntryEdit->setIcon(icons()->icon("entry-edit"));
363
    m_ui->actionEntryDelete->setIcon(icons()->icon("entry-delete"));
364
    m_ui->actionEntryRestore->setIcon(icons()->icon("entry-restore"));
365
    m_ui->actionEntryAutoType->setIcon(icons()->icon("auto-type"));
366
    m_ui->actionEntryAutoTypeSequence->setIcon(icons()->icon("auto-type"));
367
    m_ui->actionEntryAutoTypeUsername->setIcon(icons()->icon("auto-type"));
368
    m_ui->actionEntryAutoTypeUsernameEnter->setIcon(icons()->icon("auto-type"));
369
    m_ui->actionEntryAutoTypePassword->setIcon(icons()->icon("auto-type"));
370
    m_ui->actionEntryAutoTypePasswordEnter->setIcon(icons()->icon("auto-type"));
371
    m_ui->actionEntryAutoTypeTOTP->setIcon(icons()->icon("auto-type"));
372
    m_ui->actionEntryMoveUp->setIcon(icons()->icon("move-up"));
373
    m_ui->actionEntryMoveDown->setIcon(icons()->icon("move-down"));
374
    m_ui->actionEntryCopyUsername->setIcon(icons()->icon("username-copy"));
375
    m_ui->actionEntryCopyPassword->setIcon(icons()->icon("password-copy"));
376
    m_ui->actionEntryCopyURL->setIcon(icons()->icon("url-copy"));
377
    m_ui->menuEntryCopyAttribute->setIcon(icons()->icon("attributes-copy"));
378
    m_ui->menuEntryTotp->setIcon(icons()->icon("totp"));
379
    m_ui->actionEntryTotp->setIcon(icons()->icon("totp"));
380
    m_ui->actionEntryCopyTotp->setIcon(icons()->icon("totp-copy"));
381
    m_ui->actionEntryCopyPasswordTotp->setIcon(icons()->icon("totp-copy-password"));
382
    m_ui->actionEntryTotpQRCode->setIcon(icons()->icon("qrcode"));
383
    m_ui->actionEntrySetupTotp->setIcon(icons()->icon("totp-edit"));
384
    m_ui->actionEntryAddToAgent->setIcon(icons()->icon("utilities-terminal"));
385
    m_ui->actionEntryRemoveFromAgent->setIcon(icons()->icon("utilities-terminal"));
386
    m_ui->menuTags->setIcon(icons()->icon("tag-multiple"));
387
    m_ui->actionEntryDownloadIcon->setIcon(icons()->icon("favicon-download"));
388
    m_ui->actionGroupSortAsc->setIcon(icons()->icon("sort-alphabetical-ascending"));
389
    m_ui->actionGroupSortDesc->setIcon(icons()->icon("sort-alphabetical-descending"));
390

391
    m_ui->actionGroupNew->setIcon(icons()->icon("group-new"));
392
    m_ui->actionGroupEdit->setIcon(icons()->icon("group-edit"));
393
    m_ui->actionGroupClone->setIcon(icons()->icon("group-clone"));
394
    m_ui->actionGroupDelete->setIcon(icons()->icon("group-delete"));
395
    m_ui->actionGroupEmptyRecycleBin->setIcon(icons()->icon("group-empty-trash"));
396
    m_ui->actionEntryOpenUrl->setIcon(icons()->icon("web"));
397
    m_ui->actionGroupDownloadFavicons->setIcon(icons()->icon("favicon-download"));
398

399
    m_ui->actionSettings->setIcon(icons()->icon("configure"));
400
    m_ui->actionPasswordGenerator->setIcon(icons()->icon("password-generator"));
401

402
    m_ui->actionAbout->setIcon(icons()->icon("help-about"));
403
    m_ui->actionDonate->setIcon(icons()->icon("donate"));
404
    m_ui->actionBugReport->setIcon(icons()->icon("bugreport"));
405
    m_ui->actionGettingStarted->setIcon(icons()->icon("getting-started"));
406
    m_ui->actionUserGuide->setIcon(icons()->icon("user-guide"));
407
    m_ui->actionOnlineHelp->setIcon(icons()->icon("system-help"));
408
    m_ui->actionKeyboardShortcuts->setIcon(icons()->icon("keyboard-shortcuts"));
409
    m_ui->actionCheckForUpdates->setIcon(icons()->icon("system-software-update"));
410

411
#ifdef WITH_XC_BROWSER_PASSKEYS
412
    m_ui->actionPasskeys->setIcon(icons()->icon("passkey"));
413
    m_ui->actionImportPasskey->setIcon(icons()->icon("document-import"));
414
    m_ui->actionEntryImportPasskey->setIcon(icons()->icon("document-import"));
415
#endif
416

417
    m_actionMultiplexer.connect(
418
        SIGNAL(currentModeChanged(DatabaseWidget::Mode)), this, SLOT(setMenuActionState(DatabaseWidget::Mode)));
419
    m_actionMultiplexer.connect(SIGNAL(groupChanged()), this, SLOT(setMenuActionState()));
420
    m_actionMultiplexer.connect(SIGNAL(entrySelectionChanged()), this, SLOT(setMenuActionState()));
421
    m_actionMultiplexer.connect(SIGNAL(databaseNonDataChanged()), this, SLOT(setMenuActionState()));
422
    m_actionMultiplexer.connect(SIGNAL(groupContextMenuRequested(QPoint)), this, SLOT(showGroupContextMenu(QPoint)));
423
    m_actionMultiplexer.connect(SIGNAL(entryContextMenuRequested(QPoint)), this, SLOT(showEntryContextMenu(QPoint)));
424
    m_actionMultiplexer.connect(SIGNAL(groupChanged()), this, SLOT(updateEntryCountLabel()));
425
    m_actionMultiplexer.connect(SIGNAL(databaseUnlocked()), this, SLOT(updateEntryCountLabel()));
426
    m_actionMultiplexer.connect(SIGNAL(databaseModified()), this, SLOT(updateEntryCountLabel()));
427
    m_actionMultiplexer.connect(SIGNAL(searchModeActivated()), this, SLOT(updateEntryCountLabel()));
428
    m_actionMultiplexer.connect(SIGNAL(listModeActivated()), this, SLOT(updateEntryCountLabel()));
429

430
    // Notify search when the active database changes or gets locked
431
    connect(m_ui->tabWidget,
432
            SIGNAL(activeDatabaseChanged(DatabaseWidget*)),
433
            m_searchWidget,
434
            SLOT(databaseChanged(DatabaseWidget*)));
435
    connect(m_ui->tabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), m_searchWidget, SLOT(databaseChanged()));
436

437
    connect(m_ui->tabWidget, SIGNAL(tabNameChanged()), SLOT(updateWindowTitle()));
438
    connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(updateWindowTitle()));
439
    connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(databaseTabChanged(int)));
440
    connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(setMenuActionState()));
441
    connect(m_ui->tabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), SLOT(databaseStatusChanged(DatabaseWidget*)));
442
    connect(m_ui->tabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), SLOT(databaseStatusChanged(DatabaseWidget*)));
443
    connect(m_ui->tabWidget, SIGNAL(tabVisibilityChanged(bool)), SLOT(updateToolbarSeparatorVisibility()));
444
    connect(m_ui->stackedWidget, SIGNAL(currentChanged(int)), SLOT(setMenuActionState()));
445
    connect(m_ui->stackedWidget, SIGNAL(currentChanged(int)), SLOT(updateWindowTitle()));
446
    connect(m_ui->stackedWidget, SIGNAL(currentChanged(int)), SLOT(updateToolbarSeparatorVisibility()));
447
    connect(m_ui->settingsWidget, SIGNAL(accepted()), SLOT(applySettingsChanges()));
448
    connect(m_ui->settingsWidget, SIGNAL(settingsReset()), SLOT(applySettingsChanges()));
449
    connect(m_ui->settingsWidget, SIGNAL(accepted()), SLOT(switchToDatabases()));
450
    connect(m_ui->settingsWidget, SIGNAL(rejected()), SLOT(switchToDatabases()));
451

452
    connect(m_ui->actionDatabaseNew, SIGNAL(triggered()), m_ui->tabWidget, SLOT(newDatabase()));
453
    connect(m_ui->actionDatabaseOpen, SIGNAL(triggered()), m_ui->tabWidget, SLOT(openDatabase()));
454
    connect(m_ui->actionDatabaseSave, SIGNAL(triggered()), m_ui->tabWidget, SLOT(saveDatabase()));
455
    connect(m_ui->actionDatabaseSaveAs, SIGNAL(triggered()), m_ui->tabWidget, SLOT(saveDatabaseAs()));
456
    connect(m_ui->actionDatabaseSaveBackup, SIGNAL(triggered()), m_ui->tabWidget, SLOT(saveDatabaseBackup()));
457
    connect(m_ui->actionDatabaseClose, SIGNAL(triggered()), m_ui->tabWidget, SLOT(closeCurrentDatabaseTab()));
458
    connect(m_ui->actionDatabaseMerge, SIGNAL(triggered()), m_ui->tabWidget, SLOT(mergeDatabase()));
459
    connect(m_ui->actionDatabaseSecurity, SIGNAL(triggered()), m_ui->tabWidget, SLOT(showDatabaseSecurity()));
460
    connect(m_ui->actionReports, SIGNAL(triggered()), m_ui->tabWidget, SLOT(showDatabaseReports()));
461
    connect(m_ui->actionDatabaseSettings, SIGNAL(triggered()), m_ui->tabWidget, SLOT(showDatabaseSettings()));
462
#ifdef WITH_XC_BROWSER_PASSKEYS
463
    connect(m_ui->actionPasskeys, SIGNAL(triggered()), m_ui->tabWidget, SLOT(showPasskeys()));
464
    connect(m_ui->actionImportPasskey, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importPasskey()));
465
    connect(m_ui->actionEntryImportPasskey, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importPasskeyToEntry()));
466
#endif
467
    connect(m_ui->actionImport, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importFile()));
468
    connect(m_ui->actionExportCsv, SIGNAL(triggered()), m_ui->tabWidget, SLOT(exportToCsv()));
469
    connect(m_ui->actionExportHtml, SIGNAL(triggered()), m_ui->tabWidget, SLOT(exportToHtml()));
470
    connect(m_ui->actionExportXML, SIGNAL(triggered()), m_ui->tabWidget, SLOT(exportToXML()));
471
    connect(
472
        m_ui->actionLockDatabase, SIGNAL(triggered()), m_ui->tabWidget, SLOT(lockAndSwitchToFirstUnlockedDatabase()));
473
    connect(m_ui->actionLockDatabaseToolbar, SIGNAL(triggered()), m_ui->actionLockDatabase, SIGNAL(triggered()));
474
    connect(m_ui->actionLockAllDatabases, SIGNAL(triggered()), m_ui->tabWidget, SLOT(lockDatabases()));
475
    connect(m_ui->actionQuit, SIGNAL(triggered()), SLOT(appExit()));
476

477
    m_actionMultiplexer.connect(m_ui->actionEntryNew, SIGNAL(triggered()), SLOT(createEntry()));
478
    m_actionMultiplexer.connect(m_ui->actionEntryClone, SIGNAL(triggered()), SLOT(cloneEntry()));
479
    m_actionMultiplexer.connect(m_ui->actionEntryEdit, SIGNAL(triggered()), SLOT(switchToEntryEdit()));
480
    m_actionMultiplexer.connect(m_ui->actionEntryDelete, SIGNAL(triggered()), SLOT(deleteSelectedEntries()));
481
    m_actionMultiplexer.connect(m_ui->actionEntryRestore, SIGNAL(triggered()), SLOT(restoreSelectedEntries()));
482

483
    m_actionMultiplexer.connect(m_ui->actionEntryTotp, SIGNAL(triggered()), SLOT(showTotp()));
484
    m_actionMultiplexer.connect(m_ui->actionEntrySetupTotp, SIGNAL(triggered()), SLOT(setupTotp()));
485

486
    m_actionMultiplexer.connect(m_ui->actionEntryCopyTotp, SIGNAL(triggered()), SLOT(copyTotp()));
487
    m_actionMultiplexer.connect(m_ui->actionEntryCopyPasswordTotp, SIGNAL(triggered()), SLOT(copyPasswordTotp()));
488
    m_actionMultiplexer.connect(m_ui->actionEntryTotpQRCode, SIGNAL(triggered()), SLOT(showTotpKeyQrCode()));
489
    m_actionMultiplexer.connect(m_ui->actionEntryCopyTitle, SIGNAL(triggered()), SLOT(copyTitle()));
490
    m_actionMultiplexer.connect(m_ui->actionEntryMoveUp, SIGNAL(triggered()), SLOT(moveEntryUp()));
491
    m_actionMultiplexer.connect(m_ui->actionEntryMoveDown, SIGNAL(triggered()), SLOT(moveEntryDown()));
492
    m_actionMultiplexer.connect(m_ui->actionEntryCopyUsername, SIGNAL(triggered()), SLOT(copyUsername()));
493
    m_actionMultiplexer.connect(m_ui->actionEntryCopyPassword, SIGNAL(triggered()), SLOT(copyPassword()));
494
    m_actionMultiplexer.connect(m_ui->actionEntryCopyURL, SIGNAL(triggered()), SLOT(copyURL()));
495
    m_actionMultiplexer.connect(m_ui->actionEntryCopyNotes, SIGNAL(triggered()), SLOT(copyNotes()));
496
    m_actionMultiplexer.connect(m_ui->actionEntryAutoType, SIGNAL(triggered()), SLOT(performAutoType()));
497
    m_actionMultiplexer.connect(m_ui->actionEntryAutoTypeSequence, SIGNAL(triggered()), SLOT(performAutoType()));
498
    m_actionMultiplexer.connect(
499
        m_ui->actionEntryAutoTypeUsername, SIGNAL(triggered()), SLOT(performAutoTypeUsername()));
500
    m_actionMultiplexer.connect(
501
        m_ui->actionEntryAutoTypeUsernameEnter, SIGNAL(triggered()), SLOT(performAutoTypeUsernameEnter()));
502
    m_actionMultiplexer.connect(
503
        m_ui->actionEntryAutoTypePassword, SIGNAL(triggered()), SLOT(performAutoTypePassword()));
504
    m_actionMultiplexer.connect(
505
        m_ui->actionEntryAutoTypePasswordEnter, SIGNAL(triggered()), SLOT(performAutoTypePasswordEnter()));
506
    m_actionMultiplexer.connect(m_ui->actionEntryAutoTypeTOTP, SIGNAL(triggered()), SLOT(performAutoTypeTOTP()));
507
    m_actionMultiplexer.connect(m_ui->actionEntryOpenUrl, SIGNAL(triggered()), SLOT(openUrl()));
508
    m_actionMultiplexer.connect(m_ui->actionEntryDownloadIcon, SIGNAL(triggered()), SLOT(downloadSelectedFavicons()));
509
#ifdef WITH_XC_SSHAGENT
510
    m_actionMultiplexer.connect(m_ui->actionEntryAddToAgent, SIGNAL(triggered()), SLOT(addToAgent()));
511
    m_actionMultiplexer.connect(m_ui->actionEntryRemoveFromAgent, SIGNAL(triggered()), SLOT(removeFromAgent()));
512
#endif
513

514
    m_actionMultiplexer.connect(m_ui->actionGroupNew, SIGNAL(triggered()), SLOT(createGroup()));
515
    m_actionMultiplexer.connect(m_ui->actionGroupEdit, SIGNAL(triggered()), SLOT(switchToGroupEdit()));
516
    m_actionMultiplexer.connect(m_ui->actionGroupClone, SIGNAL(triggered()), SLOT(cloneGroup()));
517
    m_actionMultiplexer.connect(m_ui->actionGroupDelete, SIGNAL(triggered()), SLOT(deleteGroup()));
518
    m_actionMultiplexer.connect(m_ui->actionGroupEmptyRecycleBin, SIGNAL(triggered()), SLOT(emptyRecycleBin()));
519
    m_actionMultiplexer.connect(m_ui->actionGroupSortAsc, SIGNAL(triggered()), SLOT(sortGroupsAsc()));
520
    m_actionMultiplexer.connect(m_ui->actionGroupSortDesc, SIGNAL(triggered()), SLOT(sortGroupsDesc()));
521
    m_actionMultiplexer.connect(m_ui->actionGroupDownloadFavicons, SIGNAL(triggered()), SLOT(downloadAllFavicons()));
522

523
    connect(m_ui->actionSettings, SIGNAL(toggled(bool)), SLOT(switchToSettings(bool)));
524
    connect(m_ui->actionPasswordGenerator, SIGNAL(toggled(bool)), SLOT(togglePasswordGenerator(bool)));
525
    connect(m_ui->passwordGeneratorWidget, &PasswordGeneratorWidget::closed, this, [this] {
526
        togglePasswordGenerator(false);
527
    });
528
    m_ui->passwordGeneratorWidget->setStandaloneMode(true);
529

530
    connect(m_ui->welcomeWidget, SIGNAL(newDatabase()), SLOT(switchToNewDatabase()));
531
    connect(m_ui->welcomeWidget, SIGNAL(openDatabase()), SLOT(switchToOpenDatabase()));
532
    connect(m_ui->welcomeWidget, SIGNAL(openDatabaseFile(QString)), SLOT(switchToDatabaseFile(QString)));
533
    connect(m_ui->welcomeWidget, SIGNAL(importFile()), m_ui->tabWidget, SLOT(importFile()));
534

535
    connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog()));
536
    connect(m_ui->actionDonate, SIGNAL(triggered()), SLOT(openDonateUrl()));
537
    connect(m_ui->actionBugReport, SIGNAL(triggered()), SLOT(openBugReportUrl()));
538
    connect(m_ui->actionGettingStarted, SIGNAL(triggered()), SLOT(openGettingStartedGuide()));
539
    connect(m_ui->actionUserGuide, SIGNAL(triggered()), SLOT(openUserGuide()));
540
    connect(m_ui->actionOnlineHelp, SIGNAL(triggered()), SLOT(openOnlineHelp()));
541
    connect(m_ui->actionKeyboardShortcuts, SIGNAL(triggered()), SLOT(openKeyboardShortcuts()));
542
    connect(m_ui->actionAllowScreenCapture, &QAction::toggled, this, &MainWindow::setAllowScreenCapture);
543

544
    connect(osUtils, &OSUtilsBase::statusbarThemeChanged, this, &MainWindow::updateTrayIcon);
545

546
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
547
    // Install event filter for empty-area drag
548
    auto* eventFilter = new MainWindowEventFilter(this);
549
    m_ui->menubar->installEventFilter(eventFilter);
550
    m_ui->toolBar->installEventFilter(eventFilter);
551
    m_ui->tabWidget->tabBar()->installEventFilter(eventFilter);
552
    installEventFilter(eventFilter);
553
#endif
554

555
#ifdef Q_OS_MACOS
556
    setUnifiedTitleAndToolBarOnMac(true);
557
#endif
558

559
#ifdef WITH_XC_UPDATECHECK
560
    connect(m_ui->actionCheckForUpdates, SIGNAL(triggered()), SLOT(showUpdateCheckDialog()));
561
    connect(UpdateChecker::instance(),
562
            SIGNAL(updateCheckFinished(bool, QString, bool)),
563
            SLOT(hasUpdateAvailable(bool, QString, bool)));
564
    // Setup an update check every hour (checked only occur every 7 days)
565
    connect(&m_updateCheckTimer, &QTimer::timeout, this, &MainWindow::performUpdateCheck);
566
    m_updateCheckTimer.start(3.6e6);
567
    // Perform the startup update check after 500 ms
568
    QTimer::singleShot(500, this, SLOT(performUpdateCheck()));
569
#else
570
    m_ui->actionCheckForUpdates->setVisible(false);
571
#endif
572

573
#ifndef WITH_XC_NETWORKING
574
    m_ui->actionGroupDownloadFavicons->setVisible(false);
575
    m_ui->actionEntryDownloadIcon->setVisible(false);
576
#endif
577
#ifndef WITH_XC_DOCS
578
    m_ui->actionGettingStarted->setVisible(false);
579
    m_ui->actionUserGuide->setVisible(false);
580
    m_ui->actionKeyboardShortcuts->setVisible(false);
581
#endif
582

583
    // clang-format off
584
    connect(m_ui->tabWidget, SIGNAL(messageGlobal(QString,MessageWidget::MessageType)),
585
        SLOT(displayGlobalMessage(QString,MessageWidget::MessageType)));
586
    // clang-format on
587

588
    connect(m_ui->tabWidget, SIGNAL(messageDismissGlobal()), this, SLOT(hideGlobalMessage()));
589

590
#ifndef Q_OS_HAIKU
591
    m_screenLockListener = new ScreenLockListener(this);
592
    connect(m_screenLockListener, SIGNAL(screenLocked()), SLOT(handleScreenLock()));
593
#endif
594

595
    // Tray Icon setup
596
    connect(Application::instance(), SIGNAL(focusWindowChanged(QWindow*)), SLOT(focusWindowChanged(QWindow*)));
597
    m_trayIconTriggerReason = QSystemTrayIcon::Unknown;
598
    m_trayIconTriggerTimer.setSingleShot(true);
599
    connect(&m_trayIconTriggerTimer, SIGNAL(timeout()), SLOT(processTrayIconTrigger()));
600

601
    if (config()->hasAccessError()) {
602
        m_ui->globalMessageWidget->showMessage(tr("Access error for config file %1").arg(config()->getFileName()),
603
                                               MessageWidget::Error);
604
    }
605

606
    // Properly shutdown on logoff, restart, and shutdown
607
    connect(qApp, &QGuiApplication::commitDataRequest, this, [this] { m_appExitCalled = true; });
608

609
#if defined(KEEPASSXC_BUILD_TYPE_SNAPSHOT) || defined(KEEPASSXC_BUILD_TYPE_PRE_RELEASE)
610
    auto* hidePreRelWarn = new QAction(tr("Don't show again for this version"), m_ui->globalMessageWidget);
611
    m_ui->globalMessageWidget->addAction(hidePreRelWarn);
612
    auto hidePreRelWarnConn = QSharedPointer<QMetaObject::Connection>::create();
613
    *hidePreRelWarnConn = connect(m_ui->globalMessageWidget, &KMessageWidget::hideAnimationFinished, [=] {
614
        m_ui->globalMessageWidget->removeAction(hidePreRelWarn);
615
        disconnect(*hidePreRelWarnConn);
616
        hidePreRelWarn->deleteLater();
617
    });
618
    connect(hidePreRelWarn, &QAction::triggered, [=] {
619
        m_ui->globalMessageWidget->animatedHide();
620
        config()->set(Config::Messages_HidePreReleaseWarning, KEEPASSXC_VERSION);
621
    });
622
#endif
623
#if defined(KEEPASSXC_BUILD_TYPE_SNAPSHOT)
624
    if (config()->get(Config::Messages_HidePreReleaseWarning) != KEEPASSXC_VERSION) {
625
        m_ui->globalMessageWidget->showMessage(
626
            tr("WARNING: You are using an unstable build of KeePassXC.\n"
627
               "There is a high risk of corruption, maintain a backup of your databases.\n"
628
               "This version is not meant for production use."),
629
            MessageWidget::Warning,
630
            -1);
631
    }
632
#elif defined(KEEPASSXC_BUILD_TYPE_PRE_RELEASE)
633
    if (config()->get(Config::Messages_HidePreReleaseWarning) != KEEPASSXC_VERSION) {
634
        m_ui->globalMessageWidget->showMessage(
635
            tr("NOTE: You are using a pre-release version of KeePassXC.\n"
636
               "Expect some bugs and minor issues, this version is meant for testing purposes."),
637
            MessageWidget::Information,
638
            -1);
639
    }
640
#elif (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) && QT_VERSION < QT_VERSION_CHECK(5, 6, 0))
641
    if (!config()->get(Config::Messages_Qt55CompatibilityWarning).toBool()) {
642
        m_ui->globalMessageWidget->showMessage(
643
            tr("WARNING: Your Qt version may cause KeePassXC to crash with an On-Screen Keyboard.\n"
644
               "We recommend you use the AppImage available on our downloads page."),
645
            MessageWidget::Warning,
646
            -1);
647
        config()->set(Config::Messages_Qt55CompatibilityWarning, true);
648
    }
649
#endif
650

651
    connect(qApp, SIGNAL(anotherInstanceStarted()), this, SLOT(bringToFront()));
652
    connect(qApp, SIGNAL(applicationActivated()), this, SLOT(bringToFront()));
653
    connect(qApp, SIGNAL(openFile(QString)), this, SLOT(openDatabase(QString)));
654
    connect(qApp, SIGNAL(quitSignalReceived()), this, SLOT(appExit()), Qt::DirectConnection);
655

656
    // Setup the status bar
657
    statusBar()->setFixedHeight(24);
658
    m_progressBarLabel = new QLabel(statusBar());
659
    m_progressBarLabel->setVisible(false);
660
    statusBar()->addPermanentWidget(m_progressBarLabel);
661
    m_progressBar = new QProgressBar(statusBar());
662
    m_progressBar->setVisible(false);
663
    m_progressBar->setTextVisible(false);
664
    m_progressBar->setMaximumWidth(100);
665
    m_progressBar->setFixedHeight(15);
666
    m_progressBar->setMaximum(100);
667
    statusBar()->addPermanentWidget(m_progressBar);
668
    connect(clipboard(), SIGNAL(updateCountdown(int, QString)), this, SLOT(updateProgressBar(int, QString)));
669
    m_statusBarLabel = new QLabel(statusBar());
670
    m_statusBarLabel->setObjectName("statusBarLabel");
671
    statusBar()->addPermanentWidget(m_statusBarLabel);
672

673
    restoreConfigState();
674
    setMenuActionState();
675
}
676

677
MainWindow::~MainWindow()
678
{
679
#ifdef WITH_XC_SSHAGENT
680
    sshAgent()->removeAllIdentities();
681
#endif
682
}
683

684
/**
685
 * Restore the main window's state after launch
686
 */
687
void MainWindow::restoreConfigState()
688
{
689
    if (config()->get(Config::OpenPreviousDatabasesOnStartup).toBool()) {
690
        const QStringList fileNames = config()->get(Config::LastOpenedDatabases).toStringList();
691
        for (const QString& filename : fileNames) {
692
            if (!filename.isEmpty() && QFile::exists(filename)) {
693
                openDatabase(filename);
694
            }
695
        }
696
        auto lastActiveFile = config()->get(Config::LastActiveDatabase).toString();
697
        if (!lastActiveFile.isEmpty()) {
698
            openDatabase(lastActiveFile);
699
        }
700
    }
701
}
702

703
QList<DatabaseWidget*> MainWindow::getOpenDatabases()
704
{
705
    QList<DatabaseWidget*> dbWidgets;
706
    for (int i = 0; i < m_ui->tabWidget->count(); ++i) {
707
        dbWidgets << m_ui->tabWidget->databaseWidgetFromIndex(i);
708
    }
709
    return dbWidgets;
710
}
711

712
void MainWindow::showErrorMessage(const QString& message)
713
{
714
    m_ui->globalMessageWidget->showMessage(message, MessageWidget::Error);
715
}
716

717
void MainWindow::appExit()
718
{
719
    m_appExitCalled = true;
720
    close();
721
}
722

723
/**
724
 * Returns if application was built with hardware key support.
725
 * Intended to be used by 3rd-party applications using DBus.
726
 *
727
 * @return True if built with hardware key support, false otherwise
728
 */
729
bool MainWindow::isHardwareKeySupported()
730
{
731
#ifdef WITH_XC_YUBIKEY
732
    return true;
733
#else
734
    return false;
735
#endif
736
}
737

738
/**
739
 * Refreshes list of hardware keys known.
740
 * Triggers the DatabaseOpenWidget to automatically select the key last used for a database if found.
741
 * Intended to be used by 3rd-party applications using DBus.
742
 *
743
 * @return True if any key was found, false otherwise or if application lacks hardware key support
744
 */
745
bool MainWindow::refreshHardwareKeys()
746
{
747
#ifdef WITH_XC_YUBIKEY
748
    auto yk = YubiKey::instance();
749
    // find keys sync to allow returning if any key was found
750
    bool found = yk->findValidKeys();
751
    // emit signal so DatabaseOpenWidget can select last used key
752
    // emit here manually because sync findValidKeys() cannot do that properly
753
    emit yk->detectComplete(found);
754
    return found;
755
#else
756
    return false;
757
#endif
758
}
759

760
void MainWindow::updateLastDatabasesMenu()
761
{
762
    m_ui->menuRecentDatabases->clear();
763

764
    const QStringList lastDatabases = config()->get(Config::LastDatabases).toStringList();
765
    for (const QString& database : lastDatabases) {
766
        QAction* action = m_ui->menuRecentDatabases->addAction(database);
767
        action->setData(database);
768
        m_lastDatabasesActions->addAction(action);
769
    }
770
    m_ui->menuRecentDatabases->addSeparator();
771
    m_ui->menuRecentDatabases->addAction(m_clearHistoryAction);
772
}
773

774
void MainWindow::updateCopyAttributesMenu()
775
{
776
    DatabaseWidget* dbWidget = m_ui->tabWidget->currentDatabaseWidget();
777
    if (!dbWidget) {
778
        return;
779
    }
780

781
    if (dbWidget->numberOfSelectedEntries() != 1) {
782
        return;
783
    }
784

785
    QList<QAction*> actions = m_ui->menuEntryCopyAttribute->actions();
786
    for (int i = m_countDefaultAttributes; i < actions.size(); i++) {
787
        delete actions[i];
788
    }
789

790
    const QStringList customEntryAttributes = dbWidget->customEntryAttributes();
791
    for (const QString& key : customEntryAttributes) {
792
        QAction* action = m_ui->menuEntryCopyAttribute->addAction(key);
793
        action->setData(QVariant(key));
794
        m_copyAdditionalAttributeActions->addAction(action);
795
    }
796
}
797

798
void MainWindow::updateSetTagsMenu()
799
{
800
    // Remove all existing actions
801
    m_ui->menuTags->clear();
802

803
    auto dbWidget = m_ui->tabWidget->currentDatabaseWidget();
804
    if (dbWidget) {
805
        // Enumerate tags applied to the selected entries
806
        QSet<QString> selectedTags;
807
        for (auto entry : dbWidget->entryView()->selectedEntries()) {
808
            for (auto tag : entry->tagList()) {
809
                selectedTags.insert(tag);
810
            }
811
        }
812

813
        // Add known database tags as actions and set checked if
814
        // a selected entry has that tag
815
        for (auto tag : dbWidget->database()->tagList()) {
816
            auto action = m_ui->menuTags->addAction(icons()->icon("tag"), tag);
817
            action->setCheckable(true);
818
            action->setChecked(selectedTags.contains(tag));
819
            m_setTagsMenuActions->addAction(action);
820
        }
821
    }
822

823
    // If no tags exist in the database then show a tip to the user
824
    if (m_ui->menuTags->isEmpty()) {
825
        auto action = m_ui->menuTags->addAction(tr("No Tags"));
826
        action->setEnabled(false);
827
    }
828
}
829

830
void MainWindow::openRecentDatabase(QAction* action)
831
{
832
    openDatabase(action->data().toString());
833
}
834

835
void MainWindow::clearLastDatabases()
836
{
837
    config()->remove(Config::LastDatabases);
838
    bool inWelcomeWidget = (m_ui->stackedWidget->currentIndex() == 2);
839

840
    if (inWelcomeWidget) {
841
        m_ui->welcomeWidget->refreshLastDatabases();
842
    }
843
}
844

845
void MainWindow::openDatabase(const QString& filePath, const QString& password, const QString& keyfile)
846
{
847
    m_ui->tabWidget->addDatabaseTab(filePath, false, password, keyfile);
848
}
849

850
void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
851
{
852
    int currentIndex = m_ui->stackedWidget->currentIndex();
853

854
    bool inDatabaseTabWidget = (currentIndex == DatabaseTabScreen);
855
    bool inWelcomeWidget = (currentIndex == WelcomeScreen);
856
    bool inDatabaseTabWidgetOrWelcomeWidget = inDatabaseTabWidget || inWelcomeWidget;
857

858
    m_ui->actionDatabaseClose->setEnabled(true);
859
    m_ui->actionDatabaseMerge->setEnabled(inDatabaseTabWidget);
860
    m_ui->actionDatabaseNew->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
861
    m_ui->actionDatabaseOpen->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
862
    m_ui->menuRecentDatabases->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
863
    m_ui->actionImport->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
864
    m_ui->actionLockDatabase->setEnabled(m_ui->tabWidget->hasLockableDatabases());
865
    m_ui->actionLockDatabaseToolbar->setEnabled(m_ui->tabWidget->hasLockableDatabases());
866
    m_ui->actionLockAllDatabases->setEnabled(m_ui->tabWidget->hasLockableDatabases());
867

868
    if (inDatabaseTabWidget && m_ui->tabWidget->currentIndex() != -1) {
869
        DatabaseWidget* dbWidget = m_ui->tabWidget->currentDatabaseWidget();
870
        Q_ASSERT(dbWidget);
871

872
        if (mode == DatabaseWidget::Mode::None) {
873
            mode = dbWidget->currentMode();
874
        }
875

876
        switch (mode) {
877
        case DatabaseWidget::Mode::ViewMode: {
878
            bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1;
879
            bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0;
880
            bool groupSelected = dbWidget->isGroupSelected();
881
            bool currentGroupHasChildren = dbWidget->currentGroup()->hasChildren();
882
            bool currentGroupHasEntries = !dbWidget->currentGroup()->entries().isEmpty();
883
            bool recycleBinSelected = dbWidget->isRecycleBinSelected();
884
            bool sorted = dbWidget->isSorted();
885
            int entryIndex = dbWidget->currentEntryIndex();
886
            int numEntries = dbWidget->currentGroup()->entries().size();
887

888
            m_ui->actionEntryNew->setEnabled(true);
889
            m_ui->actionEntryClone->setEnabled(singleEntrySelected);
890
            m_ui->actionEntryEdit->setEnabled(singleEntrySelected);
891
            m_ui->actionEntryDelete->setEnabled(entriesSelected);
892
            m_ui->actionEntryRestore->setVisible(entriesSelected && recycleBinSelected);
893
            m_ui->actionEntryRestore->setEnabled(entriesSelected && recycleBinSelected);
894
            m_ui->actionEntryRestore->setText(tr("Restore Entry(s)", "", dbWidget->numberOfSelectedEntries()));
895
            m_ui->actionEntryRestore->setToolTip(tr("Restore Entry(s)", "", dbWidget->numberOfSelectedEntries()));
896
            m_ui->actionEntryMoveUp->setVisible(!sorted);
897
            m_ui->actionEntryMoveDown->setVisible(!sorted);
898
            m_ui->actionEntryMoveUp->setEnabled(singleEntrySelected && !sorted && entryIndex > 0);
899
            m_ui->actionEntryMoveDown->setEnabled(singleEntrySelected && !sorted && entryIndex >= 0
900
                                                  && entryIndex < numEntries - 1);
901
            m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTitle());
902
            m_ui->actionEntryCopyUsername->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUsername());
903
            // NOTE: Copy password is enabled even if the selected entry's password is blank to prevent Ctrl+C
904
            // from copying information from the currently selected cell in the entry view table.
905
            m_ui->actionEntryCopyPassword->setEnabled(singleEntrySelected);
906
            m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
907
            m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected && dbWidget->currentEntryHasNotes());
908
            m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected);
909
            m_ui->menuEntryTotp->setEnabled(singleEntrySelected);
910
            m_ui->menuTags->setEnabled(entriesSelected);
911
            m_ui->actionEntryAutoType->setEnabled(singleEntrySelected && dbWidget->currentEntryHasAutoTypeEnabled());
912
            m_ui->actionEntryAutoType->menu()->setEnabled(singleEntrySelected
913
                                                          && dbWidget->currentEntryHasAutoTypeEnabled());
914
            m_ui->actionEntryAutoTypeSequence->setText(
915
                singleEntrySelected ? dbWidget->currentSelectedEntry()->effectiveAutoTypeSequence()
916
                                    : Group::RootAutoTypeSequence);
917
            m_ui->actionEntryAutoTypeSequence->setEnabled(singleEntrySelected);
918
            m_ui->actionEntryAutoTypeUsername->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUsername());
919
            m_ui->actionEntryAutoTypeUsernameEnter->setEnabled(singleEntrySelected
920
                                                               && dbWidget->currentEntryHasUsername());
921
            m_ui->actionEntryAutoTypePassword->setEnabled(singleEntrySelected && dbWidget->currentEntryHasPassword());
922
            m_ui->actionEntryAutoTypePasswordEnter->setEnabled(singleEntrySelected
923
                                                               && dbWidget->currentEntryHasPassword());
924
            m_ui->actionEntryAutoTypeTOTP->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
925
            m_ui->actionEntryAutoTypeTOTP->setVisible(singleEntrySelected && dbWidget->currentEntryHasTotp());
926
            m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
927
            m_ui->actionEntryTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
928
            m_ui->actionEntryCopyTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
929
            m_ui->actionEntryCopyPasswordTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
930
            m_ui->actionEntrySetupTotp->setEnabled(singleEntrySelected);
931
            m_ui->actionEntryTotpQRCode->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
932
            m_ui->actionEntryDownloadIcon->setEnabled((entriesSelected && !singleEntrySelected)
933
                                                      || (singleEntrySelected && dbWidget->currentEntryHasUrl()));
934
            m_ui->actionGroupNew->setEnabled(groupSelected);
935
            m_ui->actionGroupEdit->setEnabled(groupSelected);
936
            m_ui->actionGroupClone->setEnabled(groupSelected && dbWidget->canCloneCurrentGroup());
937
            m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup());
938
            m_ui->actionGroupSortAsc->setEnabled(groupSelected && currentGroupHasChildren);
939
            m_ui->actionGroupSortDesc->setEnabled(groupSelected && currentGroupHasChildren);
940
            m_ui->actionGroupEmptyRecycleBin->setVisible(recycleBinSelected);
941
            m_ui->actionGroupEmptyRecycleBin->setEnabled(recycleBinSelected);
942
#ifdef WITH_XC_NETWORKING
943
            m_ui->actionGroupDownloadFavicons->setVisible(!recycleBinSelected);
944
#endif
945
            m_ui->actionGroupDownloadFavicons->setEnabled(groupSelected && currentGroupHasEntries
946
                                                          && !recycleBinSelected);
947
            m_ui->actionDatabaseSecurity->setEnabled(true);
948
            m_ui->actionReports->setEnabled(true);
949
            m_ui->actionDatabaseSettings->setEnabled(true);
950
            m_ui->actionDatabaseSave->setEnabled(m_ui->tabWidget->canSave());
951
            m_ui->actionDatabaseSaveAs->setEnabled(true);
952
            m_ui->actionDatabaseSaveBackup->setEnabled(true);
953
            m_ui->menuExport->setEnabled(true);
954
            m_ui->actionExportCsv->setEnabled(true);
955
            m_ui->actionExportHtml->setEnabled(true);
956
            m_ui->actionExportXML->setEnabled(true);
957
            m_ui->actionDatabaseMerge->setEnabled(m_ui->tabWidget->currentIndex() != -1);
958
#ifdef WITH_XC_BROWSER_PASSKEYS
959
            m_ui->actionPasskeys->setEnabled(true);
960
            m_ui->actionImportPasskey->setEnabled(true);
961
            m_ui->actionEntryImportPasskey->setEnabled(singleEntrySelected);
962
#endif
963
#ifdef WITH_XC_SSHAGENT
964
            bool singleEntryHasSshKey =
965
                singleEntrySelected && sshAgent()->isEnabled() && dbWidget->currentEntryHasSshKey();
966
            m_ui->actionEntryAddToAgent->setVisible(singleEntryHasSshKey);
967
            m_ui->actionEntryAddToAgent->setEnabled(singleEntryHasSshKey);
968
            m_ui->actionEntryRemoveFromAgent->setVisible(singleEntryHasSshKey);
969
            m_ui->actionEntryRemoveFromAgent->setEnabled(singleEntryHasSshKey);
970
#endif
971

972
            m_searchWidgetAction->setEnabled(true);
973

974
            break;
975
        }
976
        case DatabaseWidget::Mode::EditMode:
977
        case DatabaseWidget::Mode::LockedMode: {
978
            // Enable select actions when editing an entry
979
            bool editEntryActive = dbWidget->isEntryEditActive();
980
            const auto editEntryActionsMask = QList<QAction*>({m_ui->actionEntryCopyUsername,
981
                                                               m_ui->actionEntryCopyPassword,
982
                                                               m_ui->actionEntryCopyURL,
983
                                                               m_ui->actionEntryOpenUrl,
984
                                                               m_ui->actionEntryAutoType,
985
                                                               m_ui->actionEntryDownloadIcon,
986
                                                               m_ui->actionEntryCopyNotes,
987
                                                               m_ui->actionEntryCopyTitle,
988
                                                               m_ui->menuEntryCopyAttribute->menuAction(),
989
                                                               m_ui->menuEntryTotp->menuAction(),
990
                                                               m_ui->actionEntrySetupTotp});
991

992
            auto entryActions = m_ui->menuEntries->actions();
993
            entryActions << m_ui->menuEntryCopyAttribute->actions();
994
            entryActions << m_ui->menuEntryTotp->actions();
995
            for (auto action : entryActions) {
996
                bool enabled = editEntryActive && editEntryActionsMask.contains(action);
997
                if (action->menu()) {
998
                    action->menu()->setEnabled(enabled);
999
                }
1000
                action->setEnabled(enabled);
1001
            }
1002

1003
            const auto groupActions = m_ui->menuGroups->actions();
1004
            for (auto action : groupActions) {
1005
                action->setEnabled(false);
1006
            }
1007

1008
            m_ui->actionDatabaseSecurity->setEnabled(false);
1009
            m_ui->actionReports->setEnabled(false);
1010
            m_ui->actionDatabaseSettings->setEnabled(false);
1011
            m_ui->actionDatabaseSave->setEnabled(false);
1012
            m_ui->actionDatabaseSaveAs->setEnabled(false);
1013
            m_ui->actionDatabaseSaveBackup->setEnabled(false);
1014
            m_ui->menuExport->setEnabled(false);
1015
            m_ui->actionExportCsv->setEnabled(false);
1016
            m_ui->actionExportHtml->setEnabled(false);
1017
            m_ui->actionDatabaseMerge->setEnabled(false);
1018
            // Only disable the action in the database menu so that the
1019
            // menu remains active in the toolbar, if necessary
1020
            m_ui->actionLockDatabase->setEnabled(false);
1021
            // Never show in these modes
1022
            m_ui->actionEntryMoveUp->setVisible(false);
1023
            m_ui->actionEntryMoveDown->setVisible(false);
1024
            m_ui->actionEntryRestore->setVisible(false);
1025
            m_ui->actionEntryAddToAgent->setVisible(false);
1026
            m_ui->actionEntryRemoveFromAgent->setVisible(false);
1027
            m_ui->actionGroupEmptyRecycleBin->setVisible(false);
1028

1029
#ifdef WITH_XC_BROWSER_PASSKEYS
1030
            m_ui->actionPasskeys->setEnabled(false);
1031
            m_ui->actionImportPasskey->setEnabled(false);
1032
            m_ui->actionEntryImportPasskey->setEnabled(false);
1033
#else
1034
            m_ui->actionPasskeys->setVisible(false);
1035
            m_ui->actionImportPasskey->setVisible(false);
1036
            m_ui->actionEntryImportPasskey->setVisible(false);
1037
#endif
1038

1039
            m_searchWidgetAction->setEnabled(false);
1040
            break;
1041
        }
1042
        default:
1043
            Q_ASSERT(false);
1044
        }
1045
    } else {
1046
        const auto entryActions = m_ui->menuEntries->actions();
1047
        for (auto action : entryActions) {
1048
            action->setEnabled(false);
1049
        }
1050

1051
        const auto groupActions = m_ui->menuGroups->actions();
1052
        for (auto action : groupActions) {
1053
            action->setEnabled(false);
1054
        }
1055

1056
        m_ui->actionDatabaseSecurity->setEnabled(false);
1057
        m_ui->actionReports->setEnabled(false);
1058
        m_ui->actionDatabaseSettings->setEnabled(false);
1059
        m_ui->actionDatabaseSave->setEnabled(false);
1060
        m_ui->actionDatabaseSaveAs->setEnabled(false);
1061
        m_ui->actionDatabaseSaveBackup->setEnabled(false);
1062
        m_ui->actionDatabaseClose->setEnabled(false);
1063
        m_ui->menuExport->setEnabled(false);
1064
        m_ui->actionExportCsv->setEnabled(false);
1065
        m_ui->actionExportHtml->setEnabled(false);
1066
        m_ui->actionDatabaseMerge->setEnabled(false);
1067
        // Hide entry-specific actions
1068
        m_ui->actionEntryMoveUp->setVisible(false);
1069
        m_ui->actionEntryMoveDown->setVisible(false);
1070
        m_ui->actionEntryRestore->setVisible(false);
1071
        m_ui->actionEntryAddToAgent->setVisible(false);
1072
        m_ui->actionEntryRemoveFromAgent->setVisible(false);
1073
        m_ui->actionGroupEmptyRecycleBin->setVisible(false);
1074

1075
        m_searchWidgetAction->setEnabled(false);
1076
    }
1077

1078
    if ((currentIndex == PasswordGeneratorScreen) != m_ui->actionPasswordGenerator->isChecked()) {
1079
        bool blocked = m_ui->actionPasswordGenerator->blockSignals(true);
1080
        m_ui->actionPasswordGenerator->toggle();
1081
        m_ui->actionPasswordGenerator->blockSignals(blocked);
1082
    } else if ((currentIndex == SettingsScreen) != m_ui->actionSettings->isChecked()) {
1083
        bool blocked = m_ui->actionSettings->blockSignals(true);
1084
        m_ui->actionSettings->toggle();
1085
        m_ui->actionSettings->blockSignals(blocked);
1086
    }
1087
}
1088

1089
void MainWindow::updateToolbarSeparatorVisibility()
1090
{
1091
    if (!m_showToolbarSeparator) {
1092
        m_ui->toolbarSeparator->setVisible(false);
1093
        return;
1094
    }
1095

1096
    switch (m_ui->stackedWidget->currentIndex()) {
1097
    case DatabaseTabScreen:
1098
        m_ui->toolbarSeparator->setVisible(!m_ui->tabWidget->tabBar()->isVisible()
1099
                                           && m_ui->tabWidget->tabBar()->count() == 1);
1100
        break;
1101
    case SettingsScreen:
1102
        m_ui->toolbarSeparator->setVisible(true);
1103
        break;
1104
    default:
1105
        m_ui->toolbarSeparator->setVisible(false);
1106
    }
1107
}
1108

1109
void MainWindow::updateWindowTitle()
1110
{
1111
    QString customWindowTitlePart;
1112
    int stackedWidgetIndex = m_ui->stackedWidget->currentIndex();
1113
    int tabWidgetIndex = m_ui->tabWidget->currentIndex();
1114
    bool isModified = m_ui->tabWidget->isModified(tabWidgetIndex);
1115

1116
    if (stackedWidgetIndex == DatabaseTabScreen && tabWidgetIndex != -1) {
1117
        customWindowTitlePart = m_ui->tabWidget->tabName(tabWidgetIndex);
1118
        if (isModified) {
1119
            // remove asterisk '*' from title
1120
            customWindowTitlePart.remove(customWindowTitlePart.size() - 1, 1);
1121
        }
1122
        m_ui->actionDatabaseSave->setEnabled(m_ui->tabWidget->canSave(tabWidgetIndex));
1123
    } else if (stackedWidgetIndex == 1) {
1124
        customWindowTitlePart = tr("Settings");
1125
    }
1126

1127
    QString windowTitle;
1128
    if (customWindowTitlePart.isEmpty()) {
1129
        windowTitle = BaseWindowTitle;
1130
    } else {
1131
        windowTitle = QString("%1[*] - %2").arg(customWindowTitlePart, BaseWindowTitle);
1132
    }
1133

1134
    if (customWindowTitlePart.isEmpty() || stackedWidgetIndex == 1) {
1135
        setWindowFilePath("");
1136
    } else {
1137
        setWindowFilePath(m_ui->tabWidget->databaseWidgetFromIndex(tabWidgetIndex)->database()->filePath());
1138
    }
1139

1140
    setWindowTitle(windowTitle);
1141
    setWindowModified(isModified);
1142

1143
    updateTrayIcon();
1144
}
1145

1146
void MainWindow::showAboutDialog()
1147
{
1148
    auto* aboutDialog = new AboutDialog(this);
1149
    // Auto close the about dialog before attempting database locks
1150
    if (m_ui->tabWidget->currentDatabaseWidget()) {
1151
        connect(m_ui->tabWidget->currentDatabaseWidget(),
1152
                &DatabaseWidget::databaseLockRequested,
1153
                aboutDialog,
1154
                &AboutDialog::close);
1155
    }
1156
    aboutDialog->open();
1157
}
1158

1159
void MainWindow::performUpdateCheck()
1160
{
1161
#ifdef WITH_XC_UPDATECHECK
1162
    if (!config()->get(Config::UpdateCheckMessageShown).toBool()) {
1163
        auto result =
1164
            MessageBox::question(this,
1165
                                 tr("Check for updates on startup?"),
1166
                                 tr("Would you like KeePassXC to check for updates on startup?") + "\n\n"
1167
                                     + tr("You can always check for updates manually from the application menu."),
1168
                                 MessageBox::Yes | MessageBox::No,
1169
                                 MessageBox::Yes);
1170

1171
        config()->set(Config::GUI_CheckForUpdates, (result == MessageBox::Yes));
1172
        config()->set(Config::UpdateCheckMessageShown, true);
1173
    }
1174

1175
    if (config()->get(Config::GUI_CheckForUpdates).toBool()) {
1176
        updateCheck()->checkForUpdates(false);
1177
    }
1178

1179
#endif
1180
}
1181

1182
void MainWindow::hasUpdateAvailable(bool hasUpdate, const QString& version, bool isManuallyRequested)
1183
{
1184
#ifdef WITH_XC_UPDATECHECK
1185
    if (hasUpdate && !isManuallyRequested) {
1186
        auto* updateCheckDialog = new UpdateCheckDialog(this);
1187
        updateCheckDialog->showUpdateCheckResponse(hasUpdate, version);
1188
        updateCheckDialog->show();
1189
    }
1190
#else
1191
    Q_UNUSED(hasUpdate)
1192
    Q_UNUSED(version)
1193
    Q_UNUSED(isManuallyRequested)
1194
#endif
1195
}
1196

1197
void MainWindow::showUpdateCheckDialog()
1198
{
1199
#ifdef WITH_XC_UPDATECHECK
1200
    updateCheck()->checkForUpdates(true);
1201
    auto* updateCheckDialog = new UpdateCheckDialog(this);
1202
    updateCheckDialog->show();
1203
#endif
1204
}
1205

1206
void MainWindow::customOpenUrl(QString url)
1207
{
1208
#ifdef KEEPASSXC_DIST_APPIMAGE
1209
    QProcess::execute("xdg-open", {url});
1210
#else
1211
    QDesktopServices::openUrl(QUrl(url));
1212
#endif
1213
}
1214

1215
void MainWindow::openDonateUrl()
1216
{
1217
    customOpenUrl("https://keepassxc.org/donate");
1218
}
1219

1220
void MainWindow::openBugReportUrl()
1221
{
1222
    customOpenUrl("https://github.com/keepassxreboot/keepassxc/issues");
1223
}
1224

1225
void MainWindow::openGettingStartedGuide()
1226
{
1227
    customOpenUrl(QString("file:///%1").arg(resources()->dataPath("docs/KeePassXC_GettingStarted.html")));
1228
}
1229

1230
void MainWindow::openUserGuide()
1231
{
1232
    customOpenUrl(QString("file:///%1").arg(resources()->dataPath("docs/KeePassXC_UserGuide.html")));
1233
}
1234

1235
void MainWindow::openOnlineHelp()
1236
{
1237
    customOpenUrl("https://keepassxc.org/docs/");
1238
}
1239

1240
void MainWindow::openKeyboardShortcuts()
1241
{
1242
    customOpenUrl(QString("file:///%1").arg(resources()->dataPath("docs/KeePassXC_KeyboardShortcuts.html")));
1243
}
1244

1245
void MainWindow::switchToDatabases()
1246
{
1247
    if (m_ui->tabWidget->currentIndex() == -1) {
1248
        m_ui->stackedWidget->setCurrentIndex(WelcomeScreen);
1249
    } else {
1250
        m_ui->stackedWidget->setCurrentIndex(DatabaseTabScreen);
1251
    }
1252
}
1253

1254
void MainWindow::switchToSettings(bool enabled)
1255
{
1256
    if (enabled) {
1257
        m_ui->settingsWidget->loadSettings();
1258
        m_ui->stackedWidget->setCurrentIndex(SettingsScreen);
1259
    } else {
1260
        switchToDatabases();
1261
    }
1262
}
1263

1264
void MainWindow::togglePasswordGenerator(bool enabled)
1265
{
1266
    if (enabled) {
1267
        m_ui->passwordGeneratorWidget->loadSettings();
1268
        m_ui->passwordGeneratorWidget->regeneratePassword();
1269
        m_ui->stackedWidget->setCurrentIndex(PasswordGeneratorScreen);
1270
    } else {
1271
        m_ui->passwordGeneratorWidget->saveSettings();
1272
        switchToDatabases();
1273
    }
1274
}
1275

1276
void MainWindow::switchToNewDatabase()
1277
{
1278
    m_ui->tabWidget->newDatabase();
1279
    switchToDatabases();
1280
}
1281

1282
void MainWindow::switchToOpenDatabase()
1283
{
1284
    m_ui->tabWidget->openDatabase();
1285
    switchToDatabases();
1286
}
1287

1288
void MainWindow::switchToDatabaseFile(const QString& file)
1289
{
1290
    m_ui->tabWidget->addDatabaseTab(file);
1291
    switchToDatabases();
1292
}
1293

1294
void MainWindow::databaseStatusChanged(DatabaseWidget* dbWidget)
1295
{
1296
    Q_UNUSED(dbWidget);
1297
    updateTrayIcon();
1298
}
1299

1300
/**
1301
 * Select a database tab by its index. Stays bounded to first/last tab
1302
 * on overflow unless wrap is true.
1303
 *
1304
 * @param tabIndex 0-based tab index selector
1305
 * @param wrap if true wrap around to first/last tab
1306
 */
1307
void MainWindow::selectDatabaseTab(int tabIndex, bool wrap)
1308
{
1309
    if (m_ui->stackedWidget->currentIndex() == DatabaseTabScreen) {
1310
        if (wrap) {
1311
            if (tabIndex < 0) {
1312
                tabIndex = m_ui->tabWidget->count() - 1;
1313
            } else if (tabIndex >= m_ui->tabWidget->count()) {
1314
                tabIndex = 0;
1315
            }
1316
        } else {
1317
            tabIndex = qBound(0, tabIndex, m_ui->tabWidget->count() - 1);
1318
        }
1319

1320
        m_ui->tabWidget->setCurrentIndex(tabIndex);
1321
    }
1322
}
1323

1324
void MainWindow::selectNextDatabaseTab()
1325
{
1326
    selectDatabaseTab(m_ui->tabWidget->currentIndex() + 1, true);
1327
}
1328

1329
void MainWindow::selectPreviousDatabaseTab()
1330
{
1331
    selectDatabaseTab(m_ui->tabWidget->currentIndex() - 1, true);
1332
}
1333

1334
void MainWindow::databaseTabChanged(int tabIndex)
1335
{
1336
    if (tabIndex != -1 && m_ui->stackedWidget->currentIndex() == WelcomeScreen) {
1337
        m_ui->stackedWidget->setCurrentIndex(DatabaseTabScreen);
1338
    } else if (tabIndex == -1 && m_ui->stackedWidget->currentIndex() == DatabaseTabScreen) {
1339
        m_ui->stackedWidget->setCurrentIndex(WelcomeScreen);
1340
    }
1341

1342
    m_actionMultiplexer.setCurrentObject(m_ui->tabWidget->currentDatabaseWidget());
1343
    updateEntryCountLabel();
1344
}
1345

1346
void MainWindow::showEvent(QShowEvent* event)
1347
{
1348
    Q_UNUSED(event)
1349
#ifdef Q_OS_WIN
1350
    // Qt Hack - Prevent white flicker when showing window
1351
    QTimer::singleShot(50, this, [=] { setProperty("windowOpacity", 1.0); });
1352
#endif
1353
}
1354

1355
void MainWindow::hideEvent(QHideEvent* event)
1356
{
1357
    Q_UNUSED(event)
1358
#ifdef Q_OS_WIN
1359
    // Qt Hack - Prevent white flicker when showing window
1360
    setProperty("windowOpacity", 0.0);
1361
#endif
1362
}
1363

1364
void MainWindow::closeEvent(QCloseEvent* event)
1365
{
1366
    if (m_appExiting) {
1367
        event->accept();
1368
        return;
1369
    }
1370

1371
    // Ignore event and hide to tray if this is not an actual close
1372
    // request by the system's session manager.
1373
    if (config()->get(Config::GUI_MinimizeOnClose).toBool() && !m_appExitCalled && !isHidden()
1374
        && !qApp->isSavingSession()) {
1375
        event->ignore();
1376
        hideWindow();
1377
        return;
1378
    }
1379

1380
    m_appExiting = saveLastDatabases();
1381
    if (m_appExiting) {
1382
        saveWindowInformation();
1383
        event->accept();
1384
        m_restartRequested ? kpxcApp->restart() : QApplication::quit();
1385
        return;
1386
    }
1387

1388
    m_appExitCalled = false;
1389
    m_restartRequested = false;
1390
    event->ignore();
1391
}
1392

1393
void MainWindow::changeEvent(QEvent* event)
1394
{
1395
    if ((event->type() == QEvent::WindowStateChange) && isMinimized()) {
1396
        if (isTrayIconEnabled() && config()->get(Config::GUI_MinimizeToTray).toBool()) {
1397
            event->ignore();
1398
            hide();
1399
        }
1400

1401
        if (config()->get(Config::Security_LockDatabaseMinimize).toBool()) {
1402
            m_ui->tabWidget->lockDatabasesDelayed();
1403
        }
1404
    } else {
1405
        QMainWindow::changeEvent(event);
1406
    }
1407
}
1408

1409
void MainWindow::keyPressEvent(QKeyEvent* event)
1410
{
1411
    if (!event->modifiers()) {
1412
        // Allow for direct focus of search, group view, and entry view
1413
        auto dbWidget = m_ui->tabWidget->currentDatabaseWidget();
1414
        if (dbWidget && dbWidget->isEntryViewActive()) {
1415
            if (event->key() == Qt::Key_F1) {
1416
                dbWidget->focusOnGroups(true);
1417
                return;
1418
            } else if (event->key() == Qt::Key_F2) {
1419
                dbWidget->focusOnEntries(true);
1420
                return;
1421
            } else if (event->key() == Qt::Key_F3 || event->key() == Qt::Key_F6) {
1422
                focusSearchWidget();
1423
                return;
1424
            } else if (event->key() == Qt::Key_Escape && dbWidget->isSearchActive()) {
1425
                m_searchWidget->clearSearch();
1426
                return;
1427
            }
1428
        }
1429
    }
1430

1431
    QWidget::keyPressEvent(event);
1432
}
1433

1434
bool MainWindow::focusNextPrevChild(bool next)
1435
{
1436
    // Only navigate around the main window if the database widget is showing the entry view
1437
    auto dbWidget = m_ui->tabWidget->currentDatabaseWidget();
1438
    if (dbWidget && dbWidget->isVisible() && dbWidget->isEntryViewActive()) {
1439
        // Search Widget <-> Tab Widget <-> DbWidget
1440
        if (next) {
1441
            if (m_searchWidget->hasFocus()) {
1442
                if (m_ui->tabWidget->count() > 1) {
1443
                    m_ui->tabWidget->setFocus(Qt::TabFocusReason);
1444
                } else {
1445
                    dbWidget->setFocus(Qt::TabFocusReason);
1446
                }
1447
            } else if (m_ui->tabWidget->hasFocus()) {
1448
                dbWidget->setFocus(Qt::TabFocusReason);
1449
            } else {
1450
                focusSearchWidget();
1451
            }
1452
        } else {
1453
            if (m_searchWidget->hasFocus()) {
1454
                dbWidget->setFocus(Qt::BacktabFocusReason);
1455
            } else if (m_ui->tabWidget->hasFocus()) {
1456
                focusSearchWidget();
1457
            } else {
1458
                if (m_ui->tabWidget->count() > 1) {
1459
                    m_ui->tabWidget->setFocus(Qt::BacktabFocusReason);
1460
                } else {
1461
                    focusSearchWidget();
1462
                }
1463
            }
1464
        }
1465
        return true;
1466
    }
1467

1468
    // Defer to Qt to make a decision, this maintains normal behavior
1469
    return QMainWindow::focusNextPrevChild(next);
1470
}
1471

1472
void MainWindow::focusSearchWidget()
1473
{
1474
    if (m_searchWidgetAction->isEnabled()) {
1475
        m_ui->toolBar->setVisible(true);
1476
        m_ui->toolBar->setExpanded(true);
1477
        m_searchWidget->focusSearch();
1478
    }
1479
}
1480

1481
void MainWindow::saveWindowInformation()
1482
{
1483
    if (isVisible()) {
1484
        config()->set(Config::GUI_MainWindowGeometry, saveGeometry());
1485
        config()->set(Config::GUI_MainWindowState, saveState());
1486
    }
1487
}
1488

1489
bool MainWindow::saveLastDatabases()
1490
{
1491
    if (config()->get(Config::OpenPreviousDatabasesOnStartup).toBool()) {
1492
        auto currentDbWidget = m_ui->tabWidget->currentDatabaseWidget();
1493
        if (currentDbWidget) {
1494
            config()->set(Config::LastActiveDatabase, currentDbWidget->database()->filePath());
1495
        } else {
1496
            config()->remove(Config::LastActiveDatabase);
1497
        }
1498

1499
        QStringList openDatabases;
1500
        for (int i = 0; i < m_ui->tabWidget->count(); ++i) {
1501
            auto dbWidget = m_ui->tabWidget->databaseWidgetFromIndex(i);
1502
            openDatabases.append(QDir::toNativeSeparators(dbWidget->database()->filePath()));
1503
        }
1504

1505
        config()->set(Config::LastOpenedDatabases, openDatabases);
1506
    } else {
1507
        config()->remove(Config::LastActiveDatabase);
1508
        config()->remove(Config::LastOpenedDatabases);
1509
    }
1510

1511
    return m_ui->tabWidget->closeAllDatabaseTabs();
1512
}
1513

1514
void MainWindow::updateTrayIcon()
1515
{
1516
    if (config()->get(Config::GUI_ShowTrayIcon).toBool()) {
1517
        if (!m_trayIcon) {
1518
            m_trayIcon = new QSystemTrayIcon(this);
1519
            auto* menu = new QMenu(this);
1520

1521
            auto* actionToggle = new QAction(tr("Toggle window"), menu);
1522
            menu->addAction(actionToggle);
1523
            actionToggle->setIcon(icons()->icon("keepassxc-monochrome-dark"));
1524

1525
            menu->addAction(m_ui->actionLockAllDatabases);
1526

1527
#ifdef Q_OS_MACOS
1528
            auto actionQuit = new QAction(tr("Quit KeePassXC"), menu);
1529
            connect(actionQuit, SIGNAL(triggered()), SLOT(appExit()));
1530
            menu->addAction(actionQuit);
1531
#else
1532
            menu->addAction(m_ui->actionQuit);
1533
#endif
1534
            m_trayIcon->setContextMenu(menu);
1535

1536
            connect(m_trayIcon,
1537
                    SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
1538
                    SLOT(trayIconTriggered(QSystemTrayIcon::ActivationReason)));
1539
            connect(actionToggle, SIGNAL(triggered()), SLOT(toggleWindow()));
1540
        }
1541

1542
        bool showUnlocked = m_ui->tabWidget->hasLockableDatabases();
1543
        m_trayIcon->setIcon(icons()->trayIcon(showUnlocked));
1544
        m_trayIcon->setToolTip(windowTitle().replace("[*]", isWindowModified() ? "*" : ""));
1545
        m_trayIcon->show();
1546

1547
        if (!isTrayIconEnabled() || !QSystemTrayIcon::isSystemTrayAvailable()) {
1548
            // Try to show tray icon after 5 seconds, try 5 times
1549
            // This can happen if KeePassXC starts before the system tray is available
1550
            static int trayIconAttempts = 0;
1551
            if (trayIconAttempts < 5) {
1552
                QTimer::singleShot(5000, this, &MainWindow::updateTrayIcon);
1553
                ++trayIconAttempts;
1554
            }
1555
        }
1556
    } else {
1557
        if (m_trayIcon) {
1558
            m_trayIcon->hide();
1559
            delete m_trayIcon;
1560
        }
1561
    }
1562

1563
    QApplication::setQuitOnLastWindowClosed(!isTrayIconEnabled());
1564
}
1565

1566
void MainWindow::updateProgressBar(int percentage, QString message)
1567
{
1568
    if (percentage < 0) {
1569
        m_progressBar->setVisible(false);
1570
        m_progressBarLabel->setVisible(false);
1571
    } else {
1572
        m_progressBar->setValue(percentage);
1573
        m_progressBar->setVisible(true);
1574
        m_progressBarLabel->setText(message);
1575
        m_progressBarLabel->setVisible(true);
1576
    }
1577
}
1578

1579
void MainWindow::updateEntryCountLabel()
1580
{
1581
    auto dbWidget = m_ui->tabWidget->currentDatabaseWidget();
1582
    if (dbWidget && dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode) {
1583
        int numEntries = dbWidget->entryView()->model()->rowCount();
1584
        m_statusBarLabel->setText(tr("%1 Entry(s)", "", numEntries).arg(numEntries));
1585
    } else {
1586
        m_statusBarLabel->setText("");
1587
    }
1588
}
1589

1590
void MainWindow::obtainContextFocusLock()
1591
{
1592
    m_contextMenuFocusLock = true;
1593
}
1594

1595
void MainWindow::releaseContextFocusLock()
1596
{
1597
    m_contextMenuFocusLock = false;
1598
}
1599

1600
void MainWindow::agentEnabled(bool enabled)
1601
{
1602
    m_ui->actionEntryAddToAgent->setVisible(enabled);
1603
    m_ui->actionEntryRemoveFromAgent->setVisible(enabled);
1604
}
1605

1606
void MainWindow::showEntryContextMenu(const QPoint& globalPos)
1607
{
1608
    bool entrySelected = false;
1609
    auto dbWidget = m_ui->tabWidget->currentDatabaseWidget();
1610
    if (dbWidget) {
1611
        entrySelected = dbWidget->numberOfSelectedEntries() > 0;
1612
    }
1613

1614
    if (entrySelected) {
1615
        m_entryContextMenu->popup(globalPos);
1616
    } else {
1617
        m_entryNewContextMenu->popup(globalPos);
1618
    }
1619
}
1620

1621
void MainWindow::showGroupContextMenu(const QPoint& globalPos)
1622
{
1623
    m_ui->menuGroups->popup(globalPos);
1624
}
1625

1626
void MainWindow::applySettingsChanges()
1627
{
1628
    int timeout = config()->get(Config::Security_LockDatabaseIdleSeconds).toInt() * 1000;
1629
    if (timeout <= 0) {
1630
        timeout = 60;
1631
    }
1632

1633
    m_inactivityTimer->setInactivityTimeout(timeout);
1634
    if (config()->get(Config::Security_LockDatabaseIdle).toBool()) {
1635
        m_inactivityTimer->activate();
1636
    } else {
1637
        m_inactivityTimer->deactivate();
1638
    }
1639

1640
    m_ui->menubar->setHidden(config()->get(Config::GUI_HideMenubar).toBool());
1641
    m_ui->toolBar->setHidden(config()->get(Config::GUI_HideToolbar).toBool());
1642
    auto movable = config()->get(Config::GUI_MovableToolbar).toBool();
1643
    m_ui->toolBar->setMovable(movable);
1644
    if (!movable) {
1645
        // Move the toolbar back to the top of the main window
1646
        addToolBar(Qt::TopToolBarArea, m_ui->toolBar);
1647
    }
1648

1649
    bool isOk = false;
1650
    const auto toolButtonStyle =
1651
        static_cast<Qt::ToolButtonStyle>(config()->get(Config::GUI_ToolButtonStyle).toInt(&isOk));
1652
    if (isOk) {
1653
        m_ui->toolBar->setToolButtonStyle(toolButtonStyle);
1654
    }
1655

1656
    updateTrayIcon();
1657
}
1658

1659
void MainWindow::setAllowScreenCapture(bool state)
1660
{
1661
    m_allowScreenCapture = state;
1662
    for (auto window : qApp->topLevelWindows()) {
1663
        if (window->isVisible()) {
1664
            osUtils->setPreventScreenCapture(window, !m_allowScreenCapture);
1665
        }
1666
    }
1667
    m_ui->actionAllowScreenCapture->blockSignals(true);
1668
    m_ui->actionAllowScreenCapture->setChecked(m_allowScreenCapture);
1669
    m_ui->actionAllowScreenCapture->blockSignals(false);
1670
}
1671

1672
void MainWindow::focusWindowChanged(QWindow* window)
1673
{
1674
    if (window != windowHandle()) {
1675
        m_lastFocusOutTime = Clock::currentMilliSecondsSinceEpoch();
1676
    }
1677

1678
    if (!osUtils->setPreventScreenCapture(window, !m_allowScreenCapture) && !m_allowScreenCapture) {
1679
        displayGlobalMessage(QObject::tr("Warning: Failed to block screenshot capture on a top-level window."),
1680
                             MessageWidget::Error);
1681
    }
1682
}
1683

1684
void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason)
1685
{
1686
    if (!m_trayIconTriggerTimer.isActive()) {
1687
        m_trayIconTriggerTimer.start(150);
1688
    }
1689
    // Overcome Qt bug https://bugreports.qt.io/browse/QTBUG-69698
1690
    // Store last issued tray icon activation reason to properly
1691
    // capture doubleclick events
1692
    m_trayIconTriggerReason = reason;
1693
}
1694

1695
void MainWindow::processTrayIconTrigger()
1696
{
1697
#ifdef Q_OS_MACOS
1698
    // Do not toggle the window on macOS and just show the context menu instead.
1699
    // Right click detection doesn't seem to be working anyway
1700
    // and anything else will only trigger the context menu AND
1701
    // toggle the window at the same time, which is confusing at best.
1702
    // Showing only a context menu for tray icons seems to be best
1703
    // practice on macOS anyway, so this is probably fine.
1704
    return;
1705
#endif
1706

1707
    if (m_trayIconTriggerReason == QSystemTrayIcon::DoubleClick) {
1708
        // Always toggle window on double click
1709
        toggleWindow();
1710
    } else if (m_trayIconTriggerReason == QSystemTrayIcon::Trigger
1711
               || m_trayIconTriggerReason == QSystemTrayIcon::MiddleClick) {
1712
        // Toggle window if is not in front.
1713
#ifdef Q_OS_WIN
1714
        // If on Windows, check if focus switched within the 500 milliseconds since
1715
        // clicking the tray icon removes focus from main window.
1716
        if (isHidden() || (Clock::currentMilliSecondsSinceEpoch() - m_lastFocusOutTime) <= 500) {
1717
#else
1718
        // If on Linux, check if the window has focus.
1719
        if (hasFocus() || isHidden() || windowHandle()->isActive()) {
1720
#endif
1721
            toggleWindow();
1722
        } else {
1723
            bringToFront();
1724
        }
1725
    }
1726
}
1727

1728
void MainWindow::show()
1729
{
1730
#ifndef Q_OS_WIN
1731
    m_lastShowTime = Clock::currentMilliSecondsSinceEpoch();
1732
#endif
1733
#ifdef Q_OS_MACOS
1734
    // Unset minimize state to avoid weird fly-in effects
1735
    setWindowState(windowState() & ~Qt::WindowMinimized);
1736
    macUtils()->toggleForegroundApp(true);
1737
#endif
1738
    QMainWindow::show();
1739
}
1740

1741
void MainWindow::hide()
1742
{
1743
#ifndef Q_OS_WIN
1744
    qint64 current_time = Clock::currentMilliSecondsSinceEpoch();
1745
    if (current_time - m_lastShowTime < 50) {
1746
        return;
1747
    }
1748
#endif
1749
    QMainWindow::hide();
1750
#ifdef Q_OS_MACOS
1751
    macUtils()->toggleForegroundApp(false);
1752
#endif
1753
}
1754

1755
void MainWindow::hideWindow()
1756
{
1757
    saveWindowInformation();
1758

1759
    // Only hide if tray icon is active, otherwise window will be gone forever
1760
    if (isTrayIconEnabled()) {
1761
        // On X11, the window should NOT be minimized and hidden at the same time. See issue #1595.
1762
        // On macOS, we are skipping minimization as well to avoid playing the magic lamp animation.
1763
        if (QGuiApplication::platformName() != "xcb" && QGuiApplication::platformName() != "cocoa") {
1764
            setWindowState(windowState() | Qt::WindowMinimized);
1765
        }
1766
        hide();
1767
    } else {
1768
        showMinimized();
1769
    }
1770

1771
    if (config()->get(Config::Security_LockDatabaseMinimize).toBool()) {
1772
        m_ui->tabWidget->lockDatabasesDelayed();
1773
    }
1774
}
1775

1776
void MainWindow::minimizeOrHide()
1777
{
1778
    if (config()->get(Config::GUI_MinimizeToTray).toBool()) {
1779
        hideWindow();
1780
    } else {
1781
        showMinimized();
1782
    }
1783
}
1784

1785
void MainWindow::toggleWindow()
1786
{
1787
    if (isVisible() && !isMinimized()) {
1788
        hideWindow();
1789
    } else {
1790
        bringToFront();
1791

1792
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && !defined(QT_NO_DBUS) && (QT_VERSION < QT_VERSION_CHECK(5, 9, 0))
1793
        // re-register global D-Bus menu (needed on Ubuntu with Unity)
1794
        // see https://github.com/keepassxreboot/keepassxc/issues/271
1795
        // and https://bugreports.qt.io/browse/QTBUG-58723
1796
        // check for !isVisible(), because isNativeMenuBar() does not work with appmenu-qt5
1797
        static const auto isDesktopSessionUnity = qgetenv("XDG_CURRENT_DESKTOP") == "Unity";
1798

1799
        if (isDesktopSessionUnity && Tools::qtRuntimeVersion() < QT_VERSION_CHECK(5, 9, 0)
1800
            && !m_ui->menubar->isVisible()) {
1801
            QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("com.canonical.AppMenu.Registrar"),
1802
                                                              QStringLiteral("/com/canonical/AppMenu/Registrar"),
1803
                                                              QStringLiteral("com.canonical.AppMenu.Registrar"),
1804
                                                              QStringLiteral("RegisterWindow"));
1805
            QList<QVariant> args;
1806
            args << QVariant::fromValue(static_cast<uint32_t>(winId()))
1807
                 << QVariant::fromValue(QDBusObjectPath("/MenuBar/1"));
1808
            msg.setArguments(args);
1809
            QDBusConnection::sessionBus().send(msg);
1810
        }
1811
#endif
1812
    }
1813
}
1814

1815
void MainWindow::closeModalWindow()
1816
{
1817
    if (qApp->modalWindow()) {
1818
        qApp->modalWindow()->close();
1819
    }
1820
}
1821

1822
void MainWindow::lockDatabasesAfterInactivity()
1823
{
1824
    if (!m_ui->tabWidget->lockDatabases()) {
1825
        m_inactivityTimer->activate();
1826
    }
1827
}
1828

1829
bool MainWindow::isTrayIconEnabled() const
1830
{
1831
    return m_trayIcon && m_trayIcon->isVisible();
1832
}
1833

1834
void MainWindow::displayGlobalMessage(const QString& text,
1835
                                      MessageWidget::MessageType type,
1836
                                      bool showClosebutton,
1837
                                      int autoHideTimeout)
1838
{
1839
    m_ui->globalMessageWidget->setCloseButtonVisible(showClosebutton);
1840
    m_ui->globalMessageWidget->showMessage(text, type, autoHideTimeout);
1841
}
1842

1843
void MainWindow::displayTabMessage(const QString& text,
1844
                                   MessageWidget::MessageType type,
1845
                                   bool showClosebutton,
1846
                                   int autoHideTimeout)
1847
{
1848
    m_ui->tabWidget->currentDatabaseWidget()->showMessage(text, type, showClosebutton, autoHideTimeout);
1849
}
1850

1851
void MainWindow::hideGlobalMessage()
1852
{
1853
    m_ui->globalMessageWidget->hideMessage();
1854
}
1855

1856
void MainWindow::showYubiKeyPopup()
1857
{
1858
    displayGlobalMessage(tr("Please present or touch your YubiKey to continue…"),
1859
                         MessageWidget::Information,
1860
                         false,
1861
                         MessageWidget::DisableAutoHide);
1862
    setEnabled(false);
1863
}
1864

1865
void MainWindow::hideYubiKeyPopup()
1866
{
1867
    hideGlobalMessage();
1868
    setEnabled(true);
1869
}
1870

1871
void MainWindow::bringToFront()
1872
{
1873
    ensurePolished();
1874
    setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
1875
    show();
1876
    raise();
1877
    activateWindow();
1878
}
1879

1880
void MainWindow::handleScreenLock()
1881
{
1882
    if (config()->get(Config::Security_LockDatabaseScreenLock).toBool()) {
1883
        lockDatabasesAfterInactivity();
1884
    }
1885
}
1886

1887
QStringList MainWindow::kdbxFilesFromUrls(const QList<QUrl>& urls)
1888
{
1889
    QStringList kdbxFiles;
1890
    for (const QUrl& url : urls) {
1891
        const QFileInfo fInfo(url.toLocalFile());
1892
        const bool isKdbxFile = fInfo.isFile() && fInfo.suffix().toLower() == "kdbx";
1893
        if (isKdbxFile) {
1894
            kdbxFiles.append(fInfo.absoluteFilePath());
1895
        }
1896
    }
1897

1898
    return kdbxFiles;
1899
}
1900

1901
void MainWindow::dragEnterEvent(QDragEnterEvent* event)
1902
{
1903
    const QMimeData* mimeData = event->mimeData();
1904
    if (mimeData->hasUrls()) {
1905
        const QStringList kdbxFiles = kdbxFilesFromUrls(mimeData->urls());
1906
        if (!kdbxFiles.isEmpty()) {
1907
            event->acceptProposedAction();
1908
        }
1909
    }
1910
}
1911

1912
void MainWindow::dropEvent(QDropEvent* event)
1913
{
1914
    const QMimeData* mimeData = event->mimeData();
1915
    if (mimeData->hasUrls()) {
1916
        const QStringList kdbxFiles = kdbxFilesFromUrls(mimeData->urls());
1917
        if (!kdbxFiles.isEmpty()) {
1918
            event->acceptProposedAction();
1919
        }
1920
        for (const QString& kdbxFile : kdbxFiles) {
1921
            openDatabase(kdbxFile);
1922
        }
1923
    }
1924
}
1925

1926
void MainWindow::closeAllDatabases()
1927
{
1928
    m_ui->tabWidget->closeAllDatabaseTabs();
1929
}
1930

1931
void MainWindow::lockAllDatabases()
1932
{
1933
    lockDatabasesAfterInactivity();
1934
}
1935

1936
void MainWindow::displayDesktopNotification(const QString& msg, QString title, int msTimeoutHint)
1937
{
1938
    if (!m_trayIcon || !QSystemTrayIcon::supportsMessages()) {
1939
        return;
1940
    }
1941

1942
    if (title.isEmpty()) {
1943
        title = BaseWindowTitle;
1944
    }
1945

1946
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
1947
    m_trayIcon->showMessage(title, msg, icons()->applicationIcon(), msTimeoutHint);
1948
#else
1949
    m_trayIcon->showMessage(title, msg, QSystemTrayIcon::Information, msTimeoutHint);
1950
#endif
1951
}
1952

1953
void MainWindow::restartApp(const QString& message)
1954
{
1955
    auto ans = MessageBox::question(
1956
        this, tr("Restart Application?"), message, MessageBox::Yes | MessageBox::No, MessageBox::Yes);
1957
    if (ans == MessageBox::Yes) {
1958
        m_appExitCalled = true;
1959
        m_restartRequested = true;
1960
        close();
1961
    } else {
1962
        m_restartRequested = false;
1963
    }
1964
}
1965

1966
void MainWindow::initViewMenu()
1967
{
1968
    m_ui->actionThemeAuto->setData("auto");
1969
    m_ui->actionThemeLight->setData("light");
1970
    m_ui->actionThemeDark->setData("dark");
1971
    m_ui->actionThemeClassic->setData("classic");
1972

1973
    auto themeActions = new QActionGroup(this);
1974
    themeActions->addAction(m_ui->actionThemeAuto);
1975
    themeActions->addAction(m_ui->actionThemeLight);
1976
    themeActions->addAction(m_ui->actionThemeDark);
1977
    themeActions->addAction(m_ui->actionThemeClassic);
1978

1979
    auto theme = config()->get(Config::GUI_ApplicationTheme).toString();
1980
    for (auto action : themeActions->actions()) {
1981
        if (action->data() == theme) {
1982
            action->setChecked(true);
1983
            break;
1984
        }
1985
    }
1986

1987
    connect(themeActions, &QActionGroup::triggered, this, [this, theme](QAction* action) {
1988
        config()->set(Config::GUI_ApplicationTheme, action->data());
1989
        if ((action->data() == "classic" || theme == "classic") && action->data() != theme) {
1990
            restartApp(tr("You must restart the application to apply this setting. Would you like to restart now?"));
1991
        } else {
1992
            kpxcApp->applyTheme();
1993
        }
1994
    });
1995

1996
    bool compact = config()->get(Config::GUI_CompactMode).toBool();
1997
    m_ui->actionCompactMode->setChecked(compact);
1998
    connect(m_ui->actionCompactMode, &QAction::toggled, this, [this, compact](bool checked) {
1999
        config()->set(Config::GUI_CompactMode, checked);
2000
        if (checked != compact) {
2001
            restartApp(tr("You must restart the application to apply this setting. Would you like to restart now?"));
2002
        }
2003
    });
2004

2005
#ifdef Q_OS_MACOS
2006
    m_ui->actionShowMenubar->setVisible(false);
2007
#else
2008
    m_ui->actionShowMenubar->setChecked(!config()->get(Config::GUI_HideMenubar).toBool());
2009
    connect(m_ui->actionShowMenubar, &QAction::toggled, this, [this](bool checked) {
2010
        config()->set(Config::GUI_HideMenubar, !checked);
2011
        applySettingsChanges();
2012
    });
2013
#endif
2014

2015
    m_ui->actionShowToolbar->setChecked(!config()->get(Config::GUI_HideToolbar).toBool());
2016
    connect(m_ui->actionShowToolbar, &QAction::toggled, this, [this](bool checked) {
2017
        config()->set(Config::GUI_HideToolbar, !checked);
2018
        applySettingsChanges();
2019
    });
2020

2021
    m_ui->actionShowPreviewPanel->setChecked(!config()->get(Config::GUI_HidePreviewPanel).toBool());
2022
    connect(m_ui->actionShowPreviewPanel, &QAction::toggled, this, [](bool checked) {
2023
        config()->set(Config::GUI_HidePreviewPanel, !checked);
2024
    });
2025

2026
    connect(m_ui->actionAlwaysOnTop, &QAction::toggled, this, [this](bool checked) {
2027
        config()->set(Config::GUI_AlwaysOnTop, checked);
2028
        if (checked) {
2029
            setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
2030
        } else {
2031
            setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
2032
        }
2033
        show();
2034
    });
2035
    // Set checked after connecting to act on a toggle in state (default state is unchecked)
2036
    m_ui->actionAlwaysOnTop->setChecked(config()->get(Config::GUI_AlwaysOnTop).toBool());
2037

2038
    m_ui->actionHideUsernames->setChecked(config()->get(Config::GUI_HideUsernames).toBool());
2039
    connect(m_ui->actionHideUsernames, &QAction::toggled, this, [](bool checked) {
2040
        config()->set(Config::GUI_HideUsernames, checked);
2041
    });
2042

2043
    m_ui->actionHidePasswords->setChecked(config()->get(Config::GUI_HidePasswords).toBool());
2044
    connect(m_ui->actionHidePasswords, &QAction::toggled, this, [](bool checked) {
2045
        config()->set(Config::GUI_HidePasswords, checked);
2046
    });
2047
}
2048

2049
void MainWindow::initActionCollection()
2050
{
2051
    auto ac = ActionCollection::instance();
2052
    ac->addActions({// Database Menu
2053
                    m_ui->actionDatabaseNew,
2054
                    m_ui->actionDatabaseOpen,
2055
                    m_ui->actionDatabaseSave,
2056
                    m_ui->actionDatabaseSaveAs,
2057
                    m_ui->actionDatabaseSaveBackup,
2058
                    m_ui->actionDatabaseClose,
2059
                    m_ui->actionLockDatabase,
2060
                    m_ui->actionLockAllDatabases,
2061
                    m_ui->actionDatabaseSettings,
2062
                    m_ui->actionDatabaseSecurity,
2063
                    m_ui->actionReports,
2064
                    m_ui->actionPasskeys,
2065
                    m_ui->actionDatabaseMerge,
2066
                    m_ui->actionImportPasskey,
2067
                    m_ui->actionImportCsv,
2068
                    m_ui->actionImportOpVault,
2069
                    m_ui->actionImportKeePass1,
2070
                    m_ui->actionExportCsv,
2071
                    m_ui->actionExportHtml,
2072
                    m_ui->actionExportXML,
2073
                    m_ui->actionQuit,
2074
                    // Entry Menu
2075
                    m_ui->actionEntryNew,
2076
                    m_ui->actionEntryEdit,
2077
                    m_ui->actionEntryClone,
2078
                    m_ui->actionEntryDelete,
2079
                    m_ui->actionEntryCopyUsername,
2080
                    m_ui->actionEntryCopyPassword,
2081
                    m_ui->actionEntryCopyURL,
2082
                    m_ui->actionEntryCopyTitle,
2083
                    m_ui->actionEntryCopyNotes,
2084
                    m_ui->actionEntryTotp,
2085
                    m_ui->actionEntryTotpQRCode,
2086
                    m_ui->actionEntrySetupTotp,
2087
                    m_ui->actionEntryCopyTotp,
2088
                    m_ui->actionEntryCopyPasswordTotp,
2089
                    m_ui->actionEntryAutoTypeSequence,
2090
                    m_ui->actionEntryAutoTypeUsername,
2091
                    m_ui->actionEntryAutoTypeUsernameEnter,
2092
                    m_ui->actionEntryAutoTypePassword,
2093
                    m_ui->actionEntryAutoTypePasswordEnter,
2094
                    m_ui->actionEntryAutoTypeTOTP,
2095
                    m_ui->actionEntryDownloadIcon,
2096
                    m_ui->actionEntryOpenUrl,
2097
                    m_ui->actionEntryMoveUp,
2098
                    m_ui->actionEntryMoveDown,
2099
                    m_ui->actionEntryAddToAgent,
2100
                    m_ui->actionEntryRemoveFromAgent,
2101
                    m_ui->actionEntryRestore,
2102
                    // Group Menu
2103
                    m_ui->actionGroupNew,
2104
                    m_ui->actionGroupEdit,
2105
                    m_ui->actionGroupClone,
2106
                    m_ui->actionGroupDelete,
2107
                    m_ui->actionGroupDownloadFavicons,
2108
                    m_ui->actionGroupSortAsc,
2109
                    m_ui->actionGroupSortDesc,
2110
                    m_ui->actionGroupEmptyRecycleBin,
2111
                    // Tools Menu
2112
                    m_ui->actionPasswordGenerator,
2113
                    m_ui->actionSettings,
2114
                    // View Menu
2115
                    m_ui->actionThemeAuto,
2116
                    m_ui->actionThemeLight,
2117
                    m_ui->actionThemeDark,
2118
                    m_ui->actionThemeClassic,
2119
                    m_ui->actionCompactMode,
2120
#ifndef Q_OS_MACOS
2121
                    m_ui->actionShowMenubar,
2122
#endif
2123
                    m_ui->actionShowToolbar,
2124
                    m_ui->actionShowPreviewPanel,
2125
                    m_ui->actionAllowScreenCapture,
2126
                    m_ui->actionAlwaysOnTop,
2127
                    m_ui->actionHideUsernames,
2128
                    m_ui->actionHidePasswords,
2129
                    // Help Menu
2130
                    m_ui->actionGettingStarted,
2131
                    m_ui->actionUserGuide,
2132
                    m_ui->actionKeyboardShortcuts,
2133
                    m_ui->actionOnlineHelp,
2134
                    m_ui->actionCheckForUpdates,
2135
                    m_ui->actionDonate,
2136
                    m_ui->actionBugReport,
2137
                    m_ui->actionAbout});
2138

2139
    // Add actions whose shortcuts were set in the .ui file
2140
    for (const auto action : ac->actions()) {
2141
        if (!action->shortcut().isEmpty()) {
2142
            ac->setDefaultShortcut(action, action->shortcut());
2143
        }
2144
    }
2145

2146
    // Actions with standard shortcuts
2147
    ac->setDefaultShortcut(m_ui->actionDatabaseOpen, QKeySequence::Open, Qt::CTRL + Qt::Key_O);
2148
    ac->setDefaultShortcut(m_ui->actionDatabaseSave, QKeySequence::Save, Qt::CTRL + Qt::Key_S);
2149
    ac->setDefaultShortcut(m_ui->actionDatabaseSaveAs, QKeySequence::SaveAs, Qt::CTRL + Qt::SHIFT + Qt::Key_S);
2150
    ac->setDefaultShortcut(m_ui->actionDatabaseClose, QKeySequence::Close, Qt::CTRL + Qt::Key_W);
2151
    ac->setDefaultShortcut(m_ui->actionSettings, QKeySequence::Preferences, Qt::CTRL + Qt::Key_Comma);
2152
    ac->setDefaultShortcut(m_ui->actionQuit, QKeySequence::Quit, Qt::CTRL + Qt::Key_Q);
2153
    ac->setDefaultShortcut(m_ui->actionEntryNew, QKeySequence::New, Qt::CTRL + Qt::Key_N);
2154

2155
    // Prevent conflicts with global Mac shortcuts (force Control on all platforms)
2156
#ifdef Q_OS_MAC
2157
    auto modifier = Qt::META;
2158
#else
2159
    auto modifier = Qt::CTRL;
2160
#endif
2161

2162
    // All other actions with default shortcuts
2163
    ac->setDefaultShortcut(m_ui->actionDatabaseNew, Qt::CTRL + Qt::SHIFT + Qt::Key_N);
2164
    ac->setDefaultShortcut(m_ui->actionDatabaseSettings, Qt::CTRL + Qt::SHIFT + Qt::Key_Comma);
2165
    ac->setDefaultShortcut(m_ui->actionReports, Qt::CTRL + Qt::SHIFT + Qt::Key_R);
2166
    ac->setDefaultShortcut(m_ui->actionLockDatabase, Qt::CTRL + Qt::Key_L);
2167
    ac->setDefaultShortcut(m_ui->actionLockAllDatabases, Qt::CTRL + Qt::SHIFT + Qt::Key_L);
2168
    ac->setDefaultShortcut(m_ui->actionEntryEdit, Qt::CTRL + Qt::Key_E);
2169
    ac->setDefaultShortcut(m_ui->actionEntryDelete, Qt::CTRL + Qt::Key_D);
2170
    ac->setDefaultShortcut(m_ui->actionEntryDelete, Qt::Key_Delete);
2171
    ac->setDefaultShortcut(m_ui->actionEntryClone, Qt::CTRL + Qt::Key_K);
2172
    ac->setDefaultShortcut(m_ui->actionEntryTotp, Qt::CTRL + Qt::SHIFT + Qt::Key_T);
2173
    ac->setDefaultShortcut(m_ui->actionEntryDownloadIcon, Qt::CTRL + Qt::SHIFT + Qt::Key_D);
2174
    ac->setDefaultShortcut(m_ui->actionEntryCopyTotp, Qt::CTRL + Qt::Key_T);
2175
    ac->setDefaultShortcut(m_ui->actionEntryCopyPasswordTotp, Qt::CTRL + Qt::Key_Y);
2176
    ac->setDefaultShortcut(m_ui->actionEntryMoveUp, Qt::CTRL + Qt::ALT + Qt::Key_Up);
2177
    ac->setDefaultShortcut(m_ui->actionEntryMoveDown, Qt::CTRL + Qt::ALT + Qt::Key_Down);
2178
    ac->setDefaultShortcut(m_ui->actionEntryCopyUsername, Qt::CTRL + Qt::Key_B);
2179
    ac->setDefaultShortcut(m_ui->actionEntryCopyPassword, Qt::CTRL + Qt::Key_C);
2180
    ac->setDefaultShortcut(m_ui->actionEntryCopyTitle, Qt::CTRL + Qt::Key_I);
2181
    ac->setDefaultShortcut(m_ui->actionEntryAutoTypeSequence, Qt::CTRL + Qt::SHIFT + Qt::Key_V);
2182
    ac->setDefaultShortcut(m_ui->actionEntryOpenUrl, Qt::CTRL + Qt::SHIFT + Qt::Key_U);
2183
    ac->setDefaultShortcut(m_ui->actionEntryCopyURL, Qt::CTRL + Qt::Key_U);
2184
    ac->setDefaultShortcut(m_ui->actionEntryRestore, Qt::CTRL + Qt::Key_R);
2185
    ac->setDefaultShortcut(m_ui->actionEntryAddToAgent, modifier + Qt::Key_H);
2186
    ac->setDefaultShortcut(m_ui->actionEntryRemoveFromAgent, modifier + Qt::SHIFT + Qt::Key_H);
2187

2188
    QTimer::singleShot(1, ac, &ActionCollection::restoreShortcuts);
2189
}
2190

2191
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
2192

2193
MainWindowEventFilter::MainWindowEventFilter(QObject* parent)
2194
    : QObject(parent)
2195
{
2196
}
2197

2198
/**
2199
 * MainWindow event filter to initiate empty-area drag on the toolbar, menubar, and tabbar.
2200
 * Also shows menubar with Alt when menubar itself is hidden.
2201
 */
2202
bool MainWindowEventFilter::eventFilter(QObject* watched, QEvent* event)
2203
{
2204
    auto* mainWindow = getMainWindow();
2205
    if (!mainWindow || !mainWindow->m_ui) {
2206
        return QObject::eventFilter(watched, event);
2207
    }
2208

2209
    auto eventType = event->type();
2210
    if (eventType == QEvent::MouseButtonPress) {
2211
        auto mouseEvent = dynamic_cast<QMouseEvent*>(event);
2212
        if (watched == mainWindow->m_ui->menubar) {
2213
            if (!mainWindow->m_ui->menubar->actionAt(mouseEvent->pos())) {
2214
                mainWindow->windowHandle()->startSystemMove();
2215
                return false;
2216
            }
2217
        } else if (watched == mainWindow->m_ui->toolBar) {
2218
            if (!mainWindow->m_ui->toolBar->isMovable() || mainWindow->m_ui->toolBar->cursor() != Qt::SizeAllCursor) {
2219
                mainWindow->windowHandle()->startSystemMove();
2220
                return false;
2221
            }
2222
        } else if (watched == mainWindow->m_ui->tabWidget->tabBar()) {
2223
            if (mainWindow->m_ui->tabWidget->tabBar()->tabAt(mouseEvent->pos()) == -1) {
2224
                mainWindow->windowHandle()->startSystemMove();
2225
                return true;
2226
            }
2227
        }
2228
    } else if (eventType == QEvent::KeyRelease) {
2229
        if (watched == mainWindow) {
2230
            auto keyEvent = dynamic_cast<QKeyEvent*>(event);
2231
            if (keyEvent->key() == Qt::Key_Alt && !keyEvent->modifiers()
2232
                && config()->get(Config::GUI_HideMenubar).toBool()) {
2233
                auto menubar = mainWindow->m_ui->menubar;
2234
                menubar->setVisible(!menubar->isVisible());
2235
                if (menubar->isVisible()) {
2236
                    menubar->setActiveAction(mainWindow->m_ui->menuFile->menuAction());
2237
                }
2238
                return false;
2239
            }
2240
        }
2241
    }
2242

2243
    return QObject::eventFilter(watched, event);
2244
}
2245

2246
#endif
2247

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.