Repository: keepassxreboot/keepassxc (C++)

Issue:
Password is not copied when other text is selected in KeePassXC
## Overview
![image](https://github.com/keepassxreboot/keepassxc/assets/52935895/c387ae4a-cc2b-46a3-ac0f-5a9c465fda3c)
Looks like with selected text "Copy username" and "Copy URL" buttons works correctly, but not "Copy password" button.

## Steps to Reproduce
For the attached screenshot:
1. Select _Description_ from **Notes**.
2. Click "Copy password" button.

## Expected Behavior
The password value is copied (here "Password").


## Actual Behavior
The selected text is copied (here "Description").


## Context
```
KeePassXC - Version 2.7.8
Revision: f6757d3

Qt 5.15.11
Debugging mode is disabled.

Operating system: Windows 10 Version 2009
CPU architecture: x86_64
Kernel: winnt 10.0.19045

Enabled extensions:
- Auto-Type
- Browser Integration
- Passkeys
- SSH Agent
- KeeShare
- YubiKey
- Quick Unlock

Cryptographic libraries:
- Botan 3.1.1
```


Discussion: I have the same issue, I can not copy any kind of passwords from Keepass since yesturday. I have even updated to the latest version. I can not even copy the debug info, none of the copy methods works in the app. I can only use the auto typer.

### App version
```
KeePassXC - Version 2.7.8
Revision: f6757d3

Qt 5.15.11
Debugging mode is disabled.

Operating system: Windows 11 Version 2009
CPU architecture: x86_64
Kernel: winnt 10.0.19045

Enabled extensions:
- Auto-Type
- Browser Integration
- Passkeys
- SSH Agent
- KeeShare
- YubiKey
- Quick Unlock

Cryptographic libraries:
- Botan 3.1.1
 ```
Update: Tested Version 2.7.7 on the same system using the same extensions, and still no copy. The 2.7.6 works.
# Summary

Same here on linux as well. It seems to be a cross platform issue.

## Debug Information

```
KeePassXC - Version 2.7.8
Revision: f6757d3
Distribution: Flatpak

Qt 5.15.10
Debugging mode is disabled.

Operating system: KDE Flatpak runtime
CPU architecture: x86_64
Kernel: linux 6.1.0-21-amd64

Enabled extensions:
- Auto-Type
- Browser Integration
- Passkeys
- SSH Agent
- KeeShare
- YubiKey
- Secret Service Integration

Cryptographic libraries:
- Botan 3.3.0
```

## System Information

```
OS: Debian GNU/Linux 12 (bookworm) x86_64 
Host: ThinkPad 
Kernel: 6.1.0-21-amd64 
Shell: bash 5.2.15 
Resolution: 1920x1080 
DE: Plasma 5.27.5 
WM: kwin 
CPU: Intel i5-8365U (8) @ 4.100GHz 
GPU: Intel WhiskeyLake-U GT2 [UHD Graphics 620] 
Memory: 4490MiB / 39736MiB 
```

## Workaround

* Install version `2.7.4` from PPA repository using `apt-get`



Fix (patch):
diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp
index 4087b74e41..fb4297c6d6 100644
--- a/src/gui/DatabaseWidget.cpp
+++ b/src/gui/DatabaseWidget.cpp
@@ -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()));
@@ -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;
diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h
index 8f71a06765..6622394e1a 100644
--- a/src/gui/DatabaseWidget.h
+++ b/src/gui/DatabaseWidget.h
@@ -189,6 +189,7 @@ public slots:
     void copyURL();
     void copyNotes();
     void copyAttribute(QAction* action);
+    bool copyFocusedTextSelection();
     void filterByTag();
     void setTag(QAction* action);
     void showTotp();
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index a6a3515b4d..0bd949f10e 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -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;
@@ -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);
 }
 
diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h
index 7155bd1102..fcc90f4b06 100644
--- a/src/gui/MainWindow.h
+++ b/src/gui/MainWindow.h
@@ -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
@@ -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;
@@ -202,6 +211,7 @@ private slots:
     QTimer m_trayIconTriggerTimer;
     QSystemTrayIcon::ActivationReason m_trayIconTriggerReason;
 
+    friend class ActionEventFilter;
     friend class MainWindowEventFilter;
 };