mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-11-29 02:46:53 -05:00
Merge branch 'meta/release-preparation' into release/2.1.1
This commit is contained in:
commit
26ff528a85
31 changed files with 1068 additions and 436 deletions
|
|
@ -17,12 +17,20 @@
|
|||
*/
|
||||
|
||||
#include "Application.h"
|
||||
#include "MainWindow.h"
|
||||
|
||||
#include <QAbstractNativeEventFilter>
|
||||
#include <QFileOpenEvent>
|
||||
#include <QSocketNotifier>
|
||||
|
||||
#include "autotype/AutoType.h"
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_OSX)
|
||||
class XcbEventFilter : public QAbstractNativeEventFilter
|
||||
{
|
||||
|
|
@ -65,12 +73,18 @@ public:
|
|||
Application::Application(int& argc, char** argv)
|
||||
: QApplication(argc, argv)
|
||||
, m_mainWindow(nullptr)
|
||||
#ifdef Q_OS_UNIX
|
||||
, m_unixSignalNotifier(nullptr)
|
||||
#endif
|
||||
{
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_OSX)
|
||||
installNativeEventFilter(new XcbEventFilter());
|
||||
#elif defined(Q_OS_WIN)
|
||||
installNativeEventFilter(new WinEventFilter());
|
||||
#endif
|
||||
#if defined(Q_OS_UNIX)
|
||||
registerUnixSignals();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Application::setMainWindow(QWidget* mainWindow)
|
||||
|
|
@ -98,3 +112,57 @@ bool Application::event(QEvent* event)
|
|||
|
||||
return QApplication::event(event);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
int Application::unixSignalSocket[2];
|
||||
|
||||
void Application::registerUnixSignals()
|
||||
{
|
||||
int result = ::socketpair(AF_UNIX, SOCK_STREAM, 0, unixSignalSocket);
|
||||
Q_ASSERT(0 == result);
|
||||
if (0 != result) {
|
||||
// do not register handles when socket creation failed, otherwise
|
||||
// application will be unresponsive to signals such as SIGINT or SIGTERM
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<int> const handledSignals = { SIGQUIT, SIGINT, SIGTERM, SIGHUP };
|
||||
for (auto s: handledSignals) {
|
||||
struct sigaction sigAction;
|
||||
|
||||
sigAction.sa_handler = handleUnixSignal;
|
||||
sigemptyset(&sigAction.sa_mask);
|
||||
sigAction.sa_flags = 0 | SA_RESTART;
|
||||
sigaction(s, &sigAction, nullptr);
|
||||
}
|
||||
|
||||
m_unixSignalNotifier = new QSocketNotifier(unixSignalSocket[1], QSocketNotifier::Read, this);
|
||||
connect(m_unixSignalNotifier, SIGNAL(activated(int)), this, SLOT(quitBySignal()));
|
||||
}
|
||||
|
||||
void Application::handleUnixSignal(int sig)
|
||||
{
|
||||
switch (sig) {
|
||||
case SIGQUIT:
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
{
|
||||
char buf = 0;
|
||||
::write(unixSignalSocket[0], &buf, sizeof(buf));
|
||||
return;
|
||||
}
|
||||
case SIGHUP:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Application::quitBySignal()
|
||||
{
|
||||
m_unixSignalNotifier->setEnabled(false);
|
||||
char buf;
|
||||
::read(unixSignalSocket[1], &buf, sizeof(buf));
|
||||
|
||||
if (nullptr != m_mainWindow)
|
||||
static_cast<MainWindow*>(m_mainWindow)->appExit();
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
#include <QApplication>
|
||||
|
||||
class QSocketNotifier;
|
||||
|
||||
class Application : public QApplication
|
||||
{
|
||||
Q_OBJECT
|
||||
|
|
@ -34,8 +36,23 @@ public:
|
|||
Q_SIGNALS:
|
||||
void openFile(const QString& filename);
|
||||
|
||||
private Q_SLOTS:
|
||||
#if defined(Q_OS_UNIX)
|
||||
void quitBySignal();
|
||||
#endif
|
||||
|
||||
private:
|
||||
QWidget* m_mainWindow;
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
/**
|
||||
* Register Unix signals such as SIGINT and SIGTERM for clean shutdown.
|
||||
*/
|
||||
void registerUnixSignals();
|
||||
QSocketNotifier* m_unixSignalNotifier;
|
||||
static void handleUnixSignal(int sig);
|
||||
static int unixSignalSocket[2];
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_APPLICATION_H
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include <QAction>
|
||||
#include <QDesktopServices>
|
||||
#include <QCheckBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QFile>
|
||||
|
|
@ -312,8 +313,10 @@ void DatabaseWidget::cloneEntry()
|
|||
return;
|
||||
}
|
||||
|
||||
Entry* entry = currentEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo);
|
||||
Entry* entry = currentEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo | Entry::CloneRenameTitle);
|
||||
entry->setGroup(currentEntry->group());
|
||||
if (isInSearchMode())
|
||||
search(m_lastSearchText);
|
||||
m_entryView->setFocus();
|
||||
m_entryView->setCurrentEntry(entry);
|
||||
}
|
||||
|
|
@ -494,8 +497,46 @@ void DatabaseWidget::openUrlForEntry(Entry* entry)
|
|||
}
|
||||
|
||||
if (urlString.startsWith("cmd://")) {
|
||||
// check if decision to execute command was stored
|
||||
if (entry->attributes()->hasKey(EntryAttributes::RememberCmdExecAttr)) {
|
||||
if (entry->attributes()->value(EntryAttributes::RememberCmdExecAttr) == "1") {
|
||||
QProcess::startDetached(urlString.mid(6));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise ask user
|
||||
if (urlString.length() > 6) {
|
||||
QProcess::startDetached(urlString.mid(6));
|
||||
QString cmdTruncated = urlString.mid(6);
|
||||
if (cmdTruncated.length() > 400)
|
||||
cmdTruncated = cmdTruncated.left(400) + " […]";
|
||||
QMessageBox msgbox(QMessageBox::Icon::Question,
|
||||
tr("Execute command?"),
|
||||
tr("Do you really want to execute the following command?<br><br>%1<br>")
|
||||
.arg(cmdTruncated.toHtmlEscaped()),
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
this
|
||||
);
|
||||
msgbox.setDefaultButton(QMessageBox::No);
|
||||
|
||||
QCheckBox* checkbox = new QCheckBox(tr("Remember my choice"), &msgbox);
|
||||
msgbox.setCheckBox(checkbox);
|
||||
bool remember = false;
|
||||
QObject::connect(checkbox, &QCheckBox::stateChanged, [&](int state) {
|
||||
if (static_cast<Qt::CheckState>(state) == Qt::CheckState::Checked) {
|
||||
remember = true;
|
||||
}
|
||||
});
|
||||
|
||||
int result = msgbox.exec();
|
||||
if (result == QMessageBox::Yes) {
|
||||
QProcess::startDetached(urlString.mid(6));
|
||||
}
|
||||
|
||||
if (remember) {
|
||||
entry->attributes()->set(EntryAttributes::RememberCmdExecAttr,
|
||||
result == QMessageBox::Yes ? "1" : "0");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
@ -722,15 +763,10 @@ void DatabaseWidget::unlockDatabase(bool accepted)
|
|||
|
||||
replaceDatabase(db);
|
||||
|
||||
const QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
|
||||
for (Group* group : groups) {
|
||||
if (group->uuid() == m_groupBeforeLock) {
|
||||
m_groupView->setCurrentGroup(group);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
restoreGroupEntryFocus(m_groupBeforeLock, m_entryBeforeLock);
|
||||
m_groupBeforeLock = Uuid();
|
||||
m_entryBeforeLock = Uuid();
|
||||
|
||||
setCurrentWidget(m_mainWidget);
|
||||
m_unlockDatabaseWidget->clearForms();
|
||||
Q_EMIT unlockedDatabase();
|
||||
|
|
@ -755,7 +791,7 @@ void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::Mod
|
|||
void DatabaseWidget::switchToEntryEdit()
|
||||
{
|
||||
Entry* entry = m_entryView->currentEntry();
|
||||
Q_ASSERT(entry);
|
||||
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -766,7 +802,7 @@ void DatabaseWidget::switchToEntryEdit()
|
|||
void DatabaseWidget::switchToGroupEdit()
|
||||
{
|
||||
Group* group = m_groupView->currentGroup();
|
||||
Q_ASSERT(group);
|
||||
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -943,6 +979,10 @@ void DatabaseWidget::lock()
|
|||
m_groupBeforeLock = m_db->rootGroup()->uuid();
|
||||
}
|
||||
|
||||
if (m_entryView->currentEntry()) {
|
||||
m_entryBeforeLock = m_entryView->currentEntry()->uuid();
|
||||
}
|
||||
|
||||
clearAllWidgets();
|
||||
m_unlockDatabaseWidget->load(m_filename);
|
||||
setCurrentWidget(m_unlockDatabaseWidget);
|
||||
|
|
@ -1028,7 +1068,22 @@ void DatabaseWidget::reloadDatabaseFile()
|
|||
}
|
||||
}
|
||||
|
||||
Uuid groupBeforeReload;
|
||||
if (m_groupView && m_groupView->currentGroup()) {
|
||||
groupBeforeReload = m_groupView->currentGroup()->uuid();
|
||||
}
|
||||
else {
|
||||
groupBeforeReload = m_db->rootGroup()->uuid();
|
||||
}
|
||||
|
||||
Uuid entryBeforeReload;
|
||||
if (m_entryView && m_entryView->currentEntry()) {
|
||||
entryBeforeReload = m_entryView->currentEntry()->uuid();
|
||||
}
|
||||
|
||||
replaceDatabase(db);
|
||||
restoreGroupEntryFocus(groupBeforeReload, entryBeforeReload);
|
||||
|
||||
}
|
||||
else {
|
||||
MessageBox::critical(this, tr("Autoreload Failed"),
|
||||
|
|
@ -1061,6 +1116,35 @@ QStringList DatabaseWidget::customEntryAttributes() const
|
|||
return entry->attributes()->customKeys();
|
||||
}
|
||||
|
||||
/*
|
||||
* Restores the focus on the group and entry that was focused
|
||||
* before the database was locked or reloaded.
|
||||
*/
|
||||
void DatabaseWidget::restoreGroupEntryFocus(Uuid groupUuid, Uuid entryUuid)
|
||||
{
|
||||
Group* restoredGroup = nullptr;
|
||||
const QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
|
||||
for (Group* group : groups) {
|
||||
if (group->uuid() == groupUuid) {
|
||||
restoredGroup = group;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (restoredGroup != nullptr) {
|
||||
m_groupView->setCurrentGroup(restoredGroup);
|
||||
|
||||
const QList<Entry*> entries = restoredGroup->entries();
|
||||
for (Entry* entry : entries) {
|
||||
if (entry->uuid() == entryUuid) {
|
||||
m_entryView->setCurrentEntry(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool DatabaseWidget::isGroupSelected() const
|
||||
{
|
||||
return m_groupView->currentGroup() != nullptr;
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ private Q_SLOTS:
|
|||
// Database autoreload slots
|
||||
void onWatchedFileChanged();
|
||||
void reloadDatabaseFile();
|
||||
void restoreGroupEntryFocus(Uuid groupUuid, Uuid EntryUuid);
|
||||
|
||||
private:
|
||||
void setClipboardTextAndMinimize(const QString& text);
|
||||
|
|
@ -190,6 +191,7 @@ private:
|
|||
Group* m_newParent;
|
||||
QString m_filename;
|
||||
Uuid m_groupBeforeLock;
|
||||
Uuid m_entryBeforeLock;
|
||||
|
||||
// Search state
|
||||
QString m_lastSearchText;
|
||||
|
|
|
|||
|
|
@ -364,7 +364,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
|||
bool groupSelected = dbWidget->isGroupSelected();
|
||||
|
||||
m_ui->actionEntryNew->setEnabled(!inSearch);
|
||||
m_ui->actionEntryClone->setEnabled(singleEntrySelected && !inSearch);
|
||||
m_ui->actionEntryClone->setEnabled(singleEntrySelected);
|
||||
m_ui->actionEntryEdit->setEnabled(singleEntrySelected);
|
||||
m_ui->actionEntryDelete->setEnabled(entriesSelected);
|
||||
m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTitle());
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ public:
|
|||
public Q_SLOTS:
|
||||
void openDatabase(const QString& fileName, const QString& pw = QString(),
|
||||
const QString& keyFile = QString());
|
||||
void appExit();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
|
|
@ -68,7 +69,6 @@ private Q_SLOTS:
|
|||
void applySettingsChanges();
|
||||
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);
|
||||
void toggleWindow();
|
||||
void appExit();
|
||||
void lockDatabasesAfterInactivity();
|
||||
void repairDatabase();
|
||||
|
||||
|
|
|
|||
|
|
@ -155,15 +155,15 @@
|
|||
<addaction name="actionEntryCopyNotes"/>
|
||||
<addaction name="separator"/>
|
||||
</widget>
|
||||
<addaction name="actionEntryNew"/>
|
||||
<addaction name="actionEntryClone"/>
|
||||
<addaction name="actionEntryEdit"/>
|
||||
<addaction name="actionEntryDelete"/>
|
||||
<addaction name="actionEntryCopyUsername"/>
|
||||
<addaction name="actionEntryCopyPassword"/>
|
||||
<addaction name="menuEntryCopyAttribute"/>
|
||||
<addaction name="actionEntryAutoType"/>
|
||||
<addaction name="actionEntryOpenUrl"/>
|
||||
<addaction name="actionEntryEdit"/>
|
||||
<addaction name="actionEntryClone"/>
|
||||
<addaction name="actionEntryDelete"/>
|
||||
<addaction name="actionEntryNew"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuGroups">
|
||||
<property name="title">
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ SearchWidget::SearchWidget(QWidget *parent)
|
|||
connect(this, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear()));
|
||||
|
||||
new QShortcut(Qt::CTRL + Qt::Key_F, this, SLOT(searchFocus()), nullptr, Qt::ApplicationShortcut);
|
||||
new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()), nullptr, Qt::ApplicationShortcut);
|
||||
|
||||
m_ui->searchEdit->installEventFilter(this);
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ void EditEntryWidget::setupMain()
|
|||
add(tr("Entry"), m_mainWidget);
|
||||
|
||||
m_mainUi->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show"));
|
||||
m_mainUi->togglePasswordGeneratorButton->setIcon(filePath()->icon("actions", "password-generator", false));
|
||||
connect(m_mainUi->togglePasswordButton, SIGNAL(toggled(bool)), m_mainUi->passwordEdit, SLOT(setShowPassword(bool)));
|
||||
connect(m_mainUi->togglePasswordGeneratorButton, SIGNAL(toggled(bool)), SLOT(togglePasswordGeneratorButton(bool)));
|
||||
connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool)));
|
||||
|
|
@ -434,6 +435,9 @@ void EditEntryWidget::saveEntry()
|
|||
|
||||
void EditEntryWidget::updateEntryData(Entry* entry) const
|
||||
{
|
||||
entry->attributes()->copyCustomKeysFrom(m_entryAttributes);
|
||||
entry->attachments()->copyDataFrom(m_entryAttachments);
|
||||
|
||||
entry->setTitle(m_mainUi->titleEdit->text());
|
||||
entry->setUsername(m_mainUi->usernameEdit->text());
|
||||
entry->setUrl(m_mainUi->urlEdit->text());
|
||||
|
|
@ -443,9 +447,6 @@ void EditEntryWidget::updateEntryData(Entry* entry) const
|
|||
|
||||
entry->setNotes(m_mainUi->notesEdit->toPlainText());
|
||||
|
||||
entry->attributes()->copyCustomKeysFrom(m_entryAttributes);
|
||||
entry->attachments()->copyDataFrom(m_entryAttachments);
|
||||
|
||||
IconStruct iconStruct = m_iconsWidget->state();
|
||||
|
||||
if (iconStruct.number < 0) {
|
||||
|
|
|
|||
|
|
@ -77,9 +77,6 @@
|
|||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="togglePasswordGeneratorButton">
|
||||
<property name="text">
|
||||
<string>Generate</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue