QtPass 2.0.0
Developer documentation
Loading...
Searching...
No Matches
clipboardmanager.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
2// SPDX-License-Identifier: GPL-3.0-or-later
8#include "clipboardmanager.h"
9#include "qtpasssettings.h"
10#include "util.h"
11#include <QApplication>
12#include <QClipboard>
13#include <utility>
14
15ClipboardManager::ClipboardManager(QObject *parent) : QObject(parent) {
17 m_clearTimer.setSingleShot(true);
18 connect(&m_clearTimer, &QTimer::timeout, this, &ClipboardManager::clear);
19 connect(QApplication::instance(), &QApplication::aboutToQuit, this,
21}
22
27
28void ClipboardManager::copyIfAlways(const QString &text,
29 const QString &p_output) {
31 // Only "always copy" mode actually places text on the clipboard here; in
32 // on-demand mode the copy happens later from the field's copy button.
33 // Crucially, do not touch m_clippedText otherwise — it tracks what is
34 // currently on the clipboard so the autoclear timer knows what to clear.
35 // Overwriting it when merely showing another entry left a previously copied
36 // password on the clipboard past its autoclear window (#1607).
37 if (s.clipBoardType == Enums::CLIPBOARD_ALWAYS && !p_output.isEmpty()) {
38 copyText(text);
39 }
40}
41
42void ClipboardManager::copyText(const QString &text) {
44 QClipboard *clip = QApplication::clipboard();
45
46 QClipboard::Mode mode = QClipboard::Clipboard;
47 if (s.useSelection && clip->supportsSelection()) {
48 mode = QClipboard::Selection;
49 }
50
51 // What was there before the first secret, to put back when autoclear
52 // runs (as pass -c does). Text only, as pass keeps it: rich content comes
53 // back as its plain text. Where Qt cannot read the clipboard without focus
54 // (some Wayland compositors, a copy from the tray), "before" reads as
55 // nothing and autoclear clears instead: the safe way round.
56 if (s.useAutoclear && s.restoreClipboard) {
57 std::optional<QString> &previous = mode == QClipboard::Selection
60 const QString now = clip->text(mode);
61 // Taken once per window, at the first copy, whatever is there: a text
62 // of ours, or the very text being copied, is saved as nothing (restoring
63 // it would leave a secret on the clipboard for good), and a later copy
64 // in the window cannot take a snapshot instead.
65 if (!previous) {
66 const bool safe =
67 now != text && now != m_clippedText && !m_otherCopies.contains(now);
68 previous = safe ? now : QString();
69 }
70 }
71 forgetIfSaved(text);
72 clip->setMimeData(buildClipboardMimeData(text), mode);
73
74 m_clippedText = text;
75 emit statusMessage(tr("Copied to clipboard"));
76 if (s.useAutoclear) {
77 m_clearTimer.start();
78 }
79}
80
81void ClipboardManager::forgetIfSaved(const QString &text) {
82 // The saved text is copied as a secret now: putting it back would leave
83 // that secret on the clipboard. Emptied, not dropped, so no later copy
84 // takes a new snapshot in this window either; clear() then clears.
85 for (std::optional<QString> *previous :
87 if (*previous && **previous == text) {
88 *previous = QString();
89 }
90 }
91}
92
93void ClipboardManager::trackCopied(const QString &text) {
94 if (text.isEmpty()) {
95 return;
96 }
97 forgetIfSaved(text);
98 // Kept beside m_clippedText, not instead of it: replacing it would leave a
99 // password copied earlier on the clipboard past its autoclear (#1607).
100 constexpr qsizetype kMaxOtherCopies = 16;
101 if (text != m_clippedText && !m_otherCopies.contains(text)) {
102 // A mouse selection reports itself on every change while it grows or
103 // shrinks (the X11 primary selection); only where it ends counts, or a
104 // passing "a" would stay tracked and autoclear could take someone
105 // else's "a" later.
106 // An earlier copy still on the clipboard proper (Ctrl+C) stays tracked.
107 const QString &last =
108 m_otherCopies.isEmpty() ? QString() : m_otherCopies.last();
109 if (!last.isEmpty() && (text.contains(last) || last.contains(text)) &&
110 QApplication::clipboard()->text(QClipboard::Clipboard) != last) {
111 m_otherCopies.last() = text;
112 } else {
113 if (m_otherCopies.size() >= kMaxOtherCopies) {
114 // Make room, but never by forgetting a text that is still on the
115 // clipboard or in the selection: it would then outlive autoclear.
116 // At most two are, so the oldest of the rest can always go.
117 const QString onClipboard =
118 QApplication::clipboard()->text(QClipboard::Clipboard);
119 const QString inSelection =
120 QApplication::clipboard()->text(QClipboard::Selection);
121 for (qsizetype i = 0; i < m_otherCopies.size(); ++i) {
122 if (m_otherCopies.at(i) != onClipboard &&
123 m_otherCopies.at(i) != inSelection) {
124 m_otherCopies.removeAt(i);
125 break;
126 }
127 }
128 }
129 m_otherCopies.append(text);
130 }
131 }
132 if (QtPassSettings::load().useAutoclear) {
133 m_clearTimer.start();
134 }
135}
136
138 // Whatever happens below, the saved text is forgotten: it is the user's,
139 // and no later autoclear may put it back.
140 const std::optional<QString> previousClipboard =
141 std::exchange(m_previousClipboard, std::nullopt);
142 const std::optional<QString> previousSelection =
143 std::exchange(m_previousSelection, std::nullopt);
144 if (m_clippedText.isEmpty() && m_otherCopies.isEmpty()) {
145 // Nothing of ours is out there. The old code compared the empty tracker
146 // with an empty selection, "cleared" that and said so on every deselect.
147 return;
148 }
149 const auto ours = [this](const QString &text) {
150 return !text.isEmpty() &&
151 (text == m_clippedText || m_otherCopies.contains(text));
152 };
153 QClipboard *clipboard = QApplication::clipboard();
154 const bool restore = QtPassSettings::load().restoreClipboard;
155 bool cleared = false;
156 bool restored = false;
157 // Ours still there: the text from before comes back if there was one and
158 // what is there is copyText()'s own copy, otherwise it is cleared. A copy
159 // from a shown field (trackCopied()) is cleared on purpose, not restored
160 // over: it can come long after copyText(), when "before" is stale, and the
161 // user chose that copy themselves. Something else copied meanwhile is left
162 // alone. The setting is read now: switched off during the window, the
163 // saved text was still held until here, and is dropped unused.
164 const auto putBack = [&](QClipboard::Mode mode,
165 const std::optional<QString> &previous) {
166 const QString now = clipboard->text(mode);
167 if (!ours(now)) {
168 return;
169 }
170 if (restore && previous && !previous->isEmpty() && now == m_clippedText) {
171 clipboard->setText(*previous, mode);
172 restored = true;
173 return;
174 }
175 clipboard->clear(mode);
176 if (mode == QClipboard::Selection) {
177 clipboard->setText(QString(""), QClipboard::Selection);
178 }
179 cleared = true;
180 };
181 putBack(QClipboard::Selection, previousSelection);
182 putBack(QClipboard::Clipboard, previousClipboard);
183 emit statusMessage(restored ? tr("Clipboard restored")
184 : cleared ? tr("Clipboard cleared")
185 : tr("Clipboard not cleared"));
186 m_clippedText.clear();
187 m_otherCopies.clear();
188}
189
190auto buildClipboardMimeData(const QString &text) -> QMimeData * {
191 auto *mimeData = new QMimeData();
192 mimeData->setText(text);
193// The KDE hint (what wl-copy --sensitive sets) keeps the secret out of
194// Klipper, CopyQ and cliphist history, on the BSDs as well as on Linux.
195#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
196 mimeData->setData("x-kde-passwordManagerHint", QByteArray("secret"));
197#endif
198#ifdef Q_OS_MAC
199 mimeData->setData("application/x-nspasteboard-concealed-type", QByteArray());
200#endif
201#ifdef Q_OS_WIN
202 mimeData->setData("ExcludeClipboardContentFromMonitorProcessing",
203 dwordBytes(1));
204 mimeData->setData("CanIncludeInClipboardHistory", dwordBytes(0));
205 mimeData->setData("CanUploadToCloudClipboard", dwordBytes(0));
206#endif
207 return mimeData;
208}
void trackCopied(const QString &text)
Track text that got onto the clipboard some other way.
QStringList m_otherCopies
trackCopied() texts, cleared with the rest
void copyIfAlways(const QString &text, const QString &p_output=QString())
Handle clipboard copying for a shown entry.
void setAutoclearTimer()
Re-read the autoclear delay from the settings.
void forgetIfSaved(const QString &text)
Empty a saved previous text equal to text, now copied as a secret, so clear() does not put the secret...
void copyText(const QString &text)
Copy text into the system clipboard (or the X11 primary selection when configured) and arm the autocl...
QTimer m_clearTimer
Single-shot autoclear timer; started by copyText() when the autoclear setting is on,...
void clear()
Remove the tracked text from the clipboard if it is still there. Silent no-op when nothing is tracked...
std::optional< QString > m_previousSelection
As m_previousClipboard, for the X11 primary selection.
QString m_clippedText
The secret copyText() last put on the clipboard; clear() removes the clipboard content only while it ...
void statusMessage(const QString &message)
Something worth showing in the status bar happened.
ClipboardManager(QObject *parent=nullptr)
Construct a manager that also clears its text when the application quits.
std::optional< QString > m_previousClipboard
static auto load() -> AppSettings
Load all flat settings into an AppSettings value object.
static auto getAutoclearSeconds(const int &defaultValue=QVariant().toInt()) -> int
Get autoclear delay in seconds.
auto buildClipboardMimeData(const QString &text) -> QMimeData *
Build clipboard MIME data with platform-specific security hints.
ClipboardManager, which puts secrets on the clipboard and removes them again, and the MIME data that ...
auto buildClipboardMimeData(const QString &text) -> QMimeData *
Build clipboard MIME data with platform-specific security hints.
@ CLIPBOARD_ALWAYS
Definition enums.h:26
QtPassSettings, the QSettings singleton behind every saved setting, the profiles and the Pass backend...
Plain value-object holding all flat QtPass configuration.
Definition appsettings.h:36
bool useSelection
Use the X11 primary selection.
Definition appsettings.h:75
bool restoreClipboard
Definition appsettings.h:79
Enums::ClipBoardType clipBoardType
Clipboard copy behaviour.
Definition appsettings.h:73
bool useAutoclear
Definition appsettings.h:76
Util: static helpers for the process environment, binary discovery, store paths, URL safety and the l...
constexpr int MS_PER_SECOND
Milliseconds in a second, for timers set from a setting in seconds.
Definition util.h:25