• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IJHack / QtPass / 40328029445

26 Oct 2026 01:08PM UTC coverage: 92.851% (-0.01%) from 92.861%
40328029445

push

github

web-flow
Clipboard: set the KDE password hint on the BSDs too (#2038)

* Clipboard: set the KDE password hint on the BSDs too

buildClipboardMimeData() only set x-kde-passwordManagerHint on Linux,
so on FreeBSD and OpenBSD a copied password went into Klipper, CopyQ or
cliphist history like any other text, though those run there as well.
Set it on every Unix system except Apple's, which has its own concealed
type; it is what wl-copy --sensitive does.

The test follows the same condition and is renamed to
buildClipboardMimeDataUnix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJygpMUf461yZgfAqwMJ6d
Signed-off-by: Anne Jan Brouwer <brouwer@annejan.com>

* CHANGELOG: KDE clipboard hint on the BSDs (#2038)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJygpMUf461yZgfAqwMJ6d
Signed-off-by: Anne Jan Brouwer <brouwer@annejan.com>

---------

Signed-off-by: Anne Jan Brouwer <brouwer@annejan.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

9169 of 9875 relevant lines covered (92.85%)

193.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.42
/src/nativegrep.cpp
1
// SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3
#include "nativegrep.h"
4
#include "executor.h"
5
#include "util.h"
6
#include <QDir>
7
#include <QElapsedTimer>
8
#include <QPointer>
9
#include <QProcess>
10
#include <QRegularExpression>
11
#include <QThread>
12
#include <utility>
13

14
NativeGrep::NativeGrep(QObject *parent) : QObject(parent) {}
189 ✔
15

16
NativeGrep::~NativeGrep() {
189 ✔
17
  static constexpr int kThreadTimeoutMs = 5000;
18
  cancel();
189 ✔
19
  QElapsedTimer elapsed;
189 ✔
20
  elapsed.start();
189 ✔
21
  for (const Worker &w : std::as_const(m_workers)) {
189 ✔
22
    if (w.thread && w.thread->isRunning()) {
×
23
      const int remaining =
24
          kThreadTimeoutMs - static_cast<int>(elapsed.elapsed());
×
25
      if (remaining > 0)
×
26
        w.thread->wait(remaining);
×
27
    }
28
  }
29
}
189 ✔
30

31
/**
32
 * @brief Decrypt one .gpg file and return lines matching rx.
33
 */
34
auto NativeGrep::matchFile(const QProcessEnvironment &env,
65 ✔
35
                           const QString &gpgExe, const QString &filePath,
36
                           const QRegularExpression &rx,
37
                           const std::atomic_bool *cancel) -> QStringList {
38
  const QString translatedPath =
39
      Executor::translatePathForWsl(filePath, gpgExe);
65 ✔
40
  QString plaintext;
65 ✔
41
  // The QProcess overload is the one that takes the cancel flag; it polls
42
  // the flag while waiting and terminates (then kills) gpg from this thread,
43
  // the one that owns the process.
44
  QProcess gpg;
65 ✔
45
  gpg.setProcessEnvironment(env);
65 ✔
46
  const int rc =
47
      Executor::executeBlocking(gpg, gpgExe,
585 ✔
48
                                {"-d", "--quiet", "--yes", "--no-encrypt-to",
49
                                 "--batch", "--use-agent", translatedPath},
50
                                QString(), &plaintext, nullptr, cancel);
65 ✔
51
  if (rc != 0 || plaintext.isEmpty())
65 ✔
52
    return {};
4 ✔
53
  QStringList matches;
61 ✔
54
  for (const QString &line : plaintext.split('\n')) {
196 ✔
55
    QString candidate = line;
56
    if (candidate.endsWith('\r'))
74 ✔
57
      candidate.chop(1);
×
58
    const QString t = candidate.trimmed();
59
    if (!t.isEmpty() && candidate.contains(rx))
74 ✔
60
      matches << t;
61
  }
62
  return matches;
63
}
130 ✔
64

65
/**
66
 * @brief Walk the store, decrypt every .gpg file, collect matches.
67
 */
68
auto NativeGrep::scanStore(const QProcessEnvironment &env,
10 ✔
69
                           const QString &gpgExe, const QString &storeDir,
70
                           const QRegularExpression &rx,
71
                           const std::atomic_bool *cancel)
72
    -> QList<QPair<QString, QStringList>> {
73
  QList<QPair<QString, QStringList>> results;
10 ✔
74
  // Regular files only: a link or junction is not an entry, and searching
75
  // through one would decrypt files outside the store.
76
  const QStringList files =
77
      Util::regularFilesUnder(storeDir, QStringList() << "*.gpg");
30 ✔
78
  for (const QString &filePath : files) {
74 ✔
79
    if (QThread::currentThread()->isInterruptionRequested() ||
64 ✔
80
        (cancel != nullptr && cancel->load()))
64 ✔
81
      return {};
×
82
    const QStringList matches = matchFile(env, gpgExe, filePath, rx, cancel);
64 ✔
83
    if (!matches.isEmpty()) {
64 ✔
84
      QString entry = QDir(storeDir).relativeFilePath(filePath);
61 ✔
85
      if (entry.endsWith(QLatin1String(".gpg")))
61 ✔
86
        entry.chop(4);
61 ✔
87
      results.append({entry, matches});
61 ✔
88
    }
89
  }
90
  return results;
91
}
92

93
/**
94
 * @brief Start a search on a worker thread.
95
 *
96
 * Results are emitted on the owner's thread via QMetaObject::invokeMethod. A
97
 * sequence counter discards results from superseded searches; the previous
98
 * search is asked to stop but not waited for, since blocking the UI thread
99
 * while gpg decrypts would freeze the interface.
100
 */
101
void NativeGrep::search(const QString &pattern, bool caseInsensitive,
10 ✔
102
                        const QString &gpgExe, const QString &storeDir,
103
                        const QProcessEnvironment &env) {
104
  cancel();
10 ✔
105

106
  // Advance the sequence before any early return so in-flight workers from the
107
  // previous query fail the seq check and cannot publish stale results.
108
  const int seq = ++m_seq;
10 ✔
109

110
  // Use trimmed() rather than isEmpty(): a whitespace-only string is a valid
111
  // regex that matches every non-empty line, which is almost never intentional
112
  // and would decrypt the entire store.
113
  //
114
  // Both early returns post finished() via Qt::QueuedConnection so that the
115
  // signal is always delivered asynchronously after search() returns, matching
116
  // the contract of the threaded path.
117
  if (pattern.trimmed().isEmpty()) {
10 ✔
118
    QMetaObject::invokeMethod(
×
119
        this,
120
        [this, seq]() {
×
121
          if (m_seq == seq)
×
122
            emit finished({});
×
123
        },
×
124
        Qt::QueuedConnection);
125
    return;
1 ✔
126
  }
127

128
  const QRegularExpression rx(
129
      pattern, caseInsensitive ? QRegularExpression::CaseInsensitiveOption
130
                               : QRegularExpression::PatternOptions{});
20 ✔
131
  if (!rx.isValid()) {
10 ✔
132
    QMetaObject::invokeMethod(
1 ✔
133
        this,
134
        [this, seq]() {
1 ✔
135
          if (m_seq == seq)
1 ✔
136
            emit finished({});
2 ✔
137
        },
1 ✔
138
        Qt::QueuedConnection);
139
    return;
140
  }
141
  QPointer<NativeGrep> self(this);
142

143
  auto emitResults = [self, seq](QList<QPair<QString, QStringList>> results) {
18 ✔
144
    if (!self)
9 ✔
145
      return;
146
    QMetaObject::invokeMethod(
9 ✔
147
        self,
148
        [self, seq, results = std::move(results)]() {
27 ✔
149
          if (self && self->m_seq == seq)
18 ✔
150
            emit self->finished(results);
9 ✔
151
        },
9 ✔
152
        Qt::QueuedConnection);
153
  };
9 ✔
154

155
  auto cancelFlag = std::make_shared<std::atomic_bool>(false);
9 ✔
156
  QThread *thread = QThread::create([gpgExe, storeDir, env, rx, cancelFlag,
18 ✔
157
                                     emitResults = std::move(emitResults)]() {
158
    std::move(emitResults)(
9 ✔
159
        scanStore(env, gpgExe, storeDir, rx, cancelFlag.get()));
9 ✔
160
  });
9 ✔
161

162
  m_workers.append({thread, cancelFlag});
18 ✔
163
  connect(thread, &QThread::finished, thread, &QObject::deleteLater);
9 ✔
164
  connect(thread, &QThread::finished, this, [this, thread]() {
9 ✔
165
    m_workers.removeIf(
9 ✔
166
        [thread](const Worker &w) { return w.thread == thread; });
9 ✔
167
  });
168
  thread->start();
9 ✔
169
}
19 ✔
170

171
void NativeGrep::cancel() {
388 ✔
172
  for (const Worker &w : std::as_const(m_workers)) {
389 ✔
173
    w.cancel->store(true);
174
    if (w.thread && w.thread->isRunning())
1 ✔
175
      w.thread->requestInterruption();
1 ✔
176
  }
177
}
388 ✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc