Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Copy Password button when text is selected #10853

Merged
merged 1 commit into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 28 additions & 23 deletions src/gui/DatabaseWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -674,29 +674,6 @@ void DatabaseWidget::copyUsername()

void DatabaseWidget::copyPassword()
{
// Some platforms do not properly trap Ctrl+C copy shortcut
// if a text edit or label has focus pass the copy operation to it

bool clearClipboard = config()->get(Config::Security_ClearClipboard).toBool();

auto plainTextEdit = qobject_cast<QPlainTextEdit*>(focusWidget());
if (plainTextEdit && plainTextEdit->textCursor().hasSelection()) {
clipboard()->setText(plainTextEdit->textCursor().selectedText(), clearClipboard);
return;
}

auto label = qobject_cast<QLabel*>(focusWidget());
if (label && label->hasSelectedText()) {
clipboard()->setText(label->selectedText(), clearClipboard);
return;
}

auto textEdit = qobject_cast<QTextEdit*>(focusWidget());
if (textEdit && textEdit->textCursor().hasSelection()) {
clipboard()->setText(textEdit->textCursor().selection().toPlainText(), clearClipboard);
return;
}

auto currentEntry = currentSelectedEntry();
if (currentEntry) {
setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->password()));
Expand Down Expand Up @@ -737,6 +714,34 @@ void DatabaseWidget::copyAttribute(QAction* action)
}
}

bool DatabaseWidget::copyFocusedTextSelection()
{
// If a focused child widget has text selected, copy that text to the clipboard
// and return true. Otherwise, return false.

const bool clearClipboard = config()->get(Config::Security_ClearClipboard).toBool();

const auto plainTextEdit = qobject_cast<QPlainTextEdit*>(focusWidget());
if (plainTextEdit && plainTextEdit->textCursor().hasSelection()) {
clipboard()->setText(plainTextEdit->textCursor().selectedText(), clearClipboard);
return true;
}

const auto label = qobject_cast<QLabel*>(focusWidget());
if (label && label->hasSelectedText()) {
clipboard()->setText(label->selectedText(), clearClipboard);
return true;
}

const auto textEdit = qobject_cast<QTextEdit*>(focusWidget());
if (textEdit && textEdit->textCursor().hasSelection()) {
clipboard()->setText(textEdit->textCursor().selection().toPlainText(), clearClipboard);
return true;
}

return false;
}

void DatabaseWidget::filterByTag()
{
QStringList searchTerms;
Expand Down
1 change: 1 addition & 0 deletions src/gui/DatabaseWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ public slots:
void copyURL();
void copyNotes();
void copyAttribute(QAction* action);
bool copyFocusedTextSelection();
void filterByTag();
void setTag(QAction* action);
void showTotp();
Expand Down
30 changes: 30 additions & 0 deletions src/gui/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,28 @@
#include "mainwindowadaptor.h"
#endif

// This filter gets installed on all the QAction objects within the MainWindow.
bool ActionEventFilter::eventFilter(QObject* watched, QEvent* event)
{
auto databaseWidget = getMainWindow()->m_ui->tabWidget->currentDatabaseWidget();
if (databaseWidget && event->type() == QEvent::Shortcut) {
// We check if we got a Shortcut event that uses the same key sequence as the
// OS default copy-to-clipboard shortcut.
static const auto stdCopyShortcuts = QKeySequence::keyBindings(QKeySequence::Copy);
if (stdCopyShortcuts.contains(static_cast<QShortcutEvent*>(event)->key())) {
// If so, we ask the database widget to check if any of its sub-widgets has text
// selected, and to copy it to the clipboard if that is the case. We do this
// because that is what the user likely expects to happen, yet Qt does not
// behave like that on all platforms.
if (databaseWidget->copyFocusedTextSelection()) {
// In that case, we return true to stop further processing of this event.
return true;
}
}
}
return QObject::eventFilter(watched, event);
}

const QString MainWindow::BaseWindowTitle = "KeePassXC";

MainWindow* g_MainWindow = nullptr;
Expand Down Expand Up @@ -2214,6 +2236,14 @@ void MainWindow::initActionCollection()
ac->setDefaultShortcut(m_ui->actionEntryRemoveFromAgent, Qt::META + Qt::SHIFT + Qt::Key_H);
#endif

// Install an event filter on every action. It improves handling of keyboard
// shortcuts that match the system copy-to-clipboard key sequence; by default
// this applies to actionEntryCopyPassword, but this could differ based on
// shortcuts the user has configured, or may configure later.
for (auto action : ac->actions()) {
action->installEventFilter(&m_actionEventFilter);
}

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

Expand Down
10 changes: 10 additions & 0 deletions src/gui/MainWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ class InactivityTimer;
class SearchWidget;
class MainWindowEventFilter;

class ActionEventFilter : public QObject
{
Q_OBJECT

protected:
bool eventFilter(QObject* obj, QEvent* event) override;
};

class MainWindow : public QMainWindow
{
Q_OBJECT
Expand Down Expand Up @@ -171,6 +179,7 @@ private slots:

const QScopedPointer<Ui::MainWindow> m_ui;
SignalMultiplexer m_actionMultiplexer;
ActionEventFilter m_actionEventFilter;
QPointer<QAction> m_clearHistoryAction;
QPointer<QAction> m_searchWidgetAction;
QPointer<QMenu> m_entryContextMenu;
Expand Down Expand Up @@ -202,6 +211,7 @@ private slots:
QTimer m_trayIconTriggerTimer;
QSystemTrayIcon::ActivationReason m_trayIconTriggerReason;

friend class ActionEventFilter;
friend class MainWindowEventFilter;
};

Expand Down
13 changes: 13 additions & 0 deletions tests/gui/TestGui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,19 @@ void TestGui::testSearch()
searchTextEdit->selectAll();
QTest::keyClick(searchTextEdit, Qt::Key_C, Qt::ControlModifier);
QTRY_COMPARE(clipboard->text(), QString("someTHING"));
// Ensure password copies when clicking on copy password button despite selected text
auto copyPasswordAction = m_mainWindow->findChild<QAction*>("actionEntryCopyPassword");
QVERIFY(copyPasswordAction);
auto copyPasswordWidget = toolBar->widgetForAction(copyPasswordAction);
QVERIFY(copyPasswordWidget);
QTest::mouseClick(copyPasswordWidget, Qt::LeftButton);
QCOMPARE(clipboard->text(), searchedEntry->password());
// Deselect text and deselect entry, Ctrl+C should now do nothing
clipboard->clear();
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
entryView->clearSelection();
QTest::keyClick(searchTextEdit, Qt::Key_C, Qt::ControlModifier);
QCOMPARE(clipboard->text(), QString());

// Test case sensitive search
searchWidget->setCaseSensitive(true);
Expand Down
Loading