mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-03-27 01:03:09 +00:00
gamecenter: refactor ProcessScanner for cancellable single-pass scans
Add ProcessScanner::scan(ScanOptions) for combined env/cmdline/exe matching, extend Match with match-source flags, and make pollUntilFound() cancellation-aware. Update monitored launch matchers to use the new API.
This commit is contained in:
parent
7200ad179c
commit
f03eb95b52
3 changed files with 225 additions and 122 deletions
|
|
@ -1402,7 +1402,7 @@ QString GameCenterDaemon::launchMonitored(const QVariantMap &launchSpec, const Q
|
||||||
// Determine bootstrap command and scanner matcher
|
// Determine bootstrap command and scanner matcher
|
||||||
QString bootstrapProgram;
|
QString bootstrapProgram;
|
||||||
QStringList bootstrapArgs;
|
QStringList bootstrapArgs;
|
||||||
std::function<QList<ProcessScanner::Match>()> matcher;
|
ProcessScanner::Matcher matcher;
|
||||||
|
|
||||||
if (provider == QLatin1String("steam")) {
|
if (provider == QLatin1String("steam")) {
|
||||||
const QString appId = extractSteamAppId(command);
|
const QString appId = extractSteamAppId(command);
|
||||||
|
|
@ -1414,9 +1414,13 @@ QString GameCenterDaemon::launchMonitored(const QVariantMap &launchSpec, const Q
|
||||||
bootstrapProgram = QStringLiteral("steam");
|
bootstrapProgram = QStringLiteral("steam");
|
||||||
bootstrapArgs = {QStringLiteral("-silent"), QStringLiteral("-applaunch"), appId};
|
bootstrapArgs = {QStringLiteral("-silent"), QStringLiteral("-applaunch"), appId};
|
||||||
|
|
||||||
matcher = [appId]() -> QList<ProcessScanner::Match> {
|
matcher = [appId](const std::atomic_bool &cancelled) -> QList<ProcessScanner::Match> {
|
||||||
// Primary: look for SteamAppId=<appId> in environ
|
ProcessScanner::ScanOptions opts;
|
||||||
QList<ProcessScanner::Match> results = ProcessScanner::findByAnyEnvironment({QStringLiteral("SteamAppId"), QStringLiteral("SteamGameId")}, appId);
|
opts.envKeys = {QStringLiteral("SteamAppId"), QStringLiteral("SteamGameId")};
|
||||||
|
opts.envValue = appId;
|
||||||
|
opts.preferEnvironmentMatches = true;
|
||||||
|
|
||||||
|
QList<ProcessScanner::Match> results = ProcessScanner::scan(opts, cancelled);
|
||||||
// Filter out the steam client itself
|
// Filter out the steam client itself
|
||||||
QList<ProcessScanner::Match> filtered;
|
QList<ProcessScanner::Match> filtered;
|
||||||
for (const auto &m : std::as_const(results)) {
|
for (const auto &m : std::as_const(results)) {
|
||||||
|
|
@ -1438,11 +1442,25 @@ QString GameCenterDaemon::launchMonitored(const QVariantMap &launchSpec, const Q
|
||||||
bootstrapArgs = {QStringLiteral("lutris:rungameid/%1").arg(lutrisId)};
|
bootstrapArgs = {QStringLiteral("lutris:rungameid/%1").arg(lutrisId)};
|
||||||
|
|
||||||
// For Lutris, look for child processes with the game slug or lutris game env
|
// For Lutris, look for child processes with the game slug or lutris game env
|
||||||
matcher = [lutrisId]() -> QList<ProcessScanner::Match> {
|
matcher = [lutrisId](const std::atomic_bool &cancelled) -> QList<ProcessScanner::Match> {
|
||||||
QList<ProcessScanner::Match> results = ProcessScanner::findByEnvironment(QStringLiteral("LUTRIS_GAME_SLUG"), lutrisId);
|
ProcessScanner::ScanOptions opts;
|
||||||
if (results.isEmpty()) {
|
opts.envKeys = {QStringLiteral("LUTRIS_GAME_SLUG")};
|
||||||
results = ProcessScanner::findByCmdline(lutrisId);
|
opts.envValue = lutrisId;
|
||||||
// Filter out lutris itself
|
opts.cmdlineSubstring = lutrisId;
|
||||||
|
opts.preferEnvironmentMatches = true;
|
||||||
|
|
||||||
|
QList<ProcessScanner::Match> results = ProcessScanner::scan(opts, cancelled);
|
||||||
|
|
||||||
|
bool anyEnvMatch = false;
|
||||||
|
for (const auto &m : std::as_const(results)) {
|
||||||
|
if (m.envMatched) {
|
||||||
|
anyEnvMatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!anyEnvMatch) {
|
||||||
|
// Filter out lutris itself (only for cmdline fallback)
|
||||||
QList<ProcessScanner::Match> filtered;
|
QList<ProcessScanner::Match> filtered;
|
||||||
for (const auto &m : std::as_const(results)) {
|
for (const auto &m : std::as_const(results)) {
|
||||||
if (!m.exe.endsWith(QLatin1String("/lutris")) && !m.exe.endsWith(QLatin1String("/python3"))) {
|
if (!m.exe.endsWith(QLatin1String("/lutris")) && !m.exe.endsWith(QLatin1String("/python3"))) {
|
||||||
|
|
@ -1453,6 +1471,7 @@ QString GameCenterDaemon::launchMonitored(const QVariantMap &launchSpec, const Q
|
||||||
results = filtered;
|
results = filtered;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,19 @@
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QLoggingCategory>
|
||||||
#include <QtConcurrentRun>
|
#include <QtConcurrentRun>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
Q_LOGGING_CATEGORY(lcProcessScanner, "org.kde.alakarte.processscanner")
|
||||||
|
|
||||||
|
static QString readExeLink(const QString &pidDir);
|
||||||
|
static QByteArray readCmdline(const QString &pidDir);
|
||||||
|
static QByteArray readEnviron(const QString &pidDir);
|
||||||
|
static bool containsNullSeparatedEntry(const QByteArray &blob, const QByteArray &needle);
|
||||||
|
static QList<uint> listPids();
|
||||||
|
|
||||||
ProcessScanner::ProcessScanner(QObject *parent)
|
ProcessScanner::ProcessScanner(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
|
|
@ -16,7 +27,11 @@ ProcessScanner::ProcessScanner(QObject *parent)
|
||||||
|
|
||||||
connect(&m_deadline, &QTimer::timeout, this, [this]() {
|
connect(&m_deadline, &QTimer::timeout, this, [this]() {
|
||||||
m_timer.stop();
|
m_timer.stop();
|
||||||
m_matcher = nullptr;
|
if (m_cancelToken) {
|
||||||
|
m_cancelToken->store(true);
|
||||||
|
}
|
||||||
|
m_cancelToken.reset();
|
||||||
|
m_matcher = {};
|
||||||
++m_generation;
|
++m_generation;
|
||||||
m_scanInFlight = false;
|
m_scanInFlight = false;
|
||||||
Q_EMIT timedOut();
|
Q_EMIT timedOut();
|
||||||
|
|
@ -42,7 +57,8 @@ void ProcessScanner::startScan()
|
||||||
m_scanInFlight = true;
|
m_scanInFlight = true;
|
||||||
m_scanGeneration = gen;
|
m_scanGeneration = gen;
|
||||||
|
|
||||||
const std::function<QList<Match>()> matcher = m_matcher;
|
const Matcher matcher = m_matcher;
|
||||||
|
const std::shared_ptr<std::atomic_bool> cancelToken = m_cancelToken;
|
||||||
auto *watcher = new QFutureWatcher<QList<Match>>(this);
|
auto *watcher = new QFutureWatcher<QList<Match>>(this);
|
||||||
|
|
||||||
connect(watcher, &QFutureWatcher<QList<Match>>::finished, this, [this, watcher, gen]() {
|
connect(watcher, &QFutureWatcher<QList<Match>>::finished, this, [this, watcher, gen]() {
|
||||||
|
|
@ -60,19 +76,130 @@ void ProcessScanner::startScan()
|
||||||
if (!results.isEmpty()) {
|
if (!results.isEmpty()) {
|
||||||
m_timer.stop();
|
m_timer.stop();
|
||||||
m_deadline.stop();
|
m_deadline.stop();
|
||||||
m_matcher = nullptr;
|
if (m_cancelToken) {
|
||||||
|
m_cancelToken->store(true);
|
||||||
|
}
|
||||||
|
m_cancelToken.reset();
|
||||||
|
m_matcher = {};
|
||||||
Q_EMIT found(results);
|
Q_EMIT found(results);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watcher->setFuture(QtConcurrent::run([matcher]() {
|
watcher->setFuture(QtConcurrent::run([matcher, cancelToken]() {
|
||||||
|
static const std::atomic_bool neverCancelled{false};
|
||||||
|
const std::atomic_bool &cancelled = cancelToken ? *cancelToken : neverCancelled;
|
||||||
if (!matcher) {
|
if (!matcher) {
|
||||||
return QList<Match>{};
|
return QList<Match>{};
|
||||||
}
|
}
|
||||||
return matcher();
|
return matcher(cancelled);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<ProcessScanner::Match> ProcessScanner::scan(const ScanOptions &options)
|
||||||
|
{
|
||||||
|
static const std::atomic_bool neverCancelled{false};
|
||||||
|
return scan(options, neverCancelled);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ProcessScanner::Match> ProcessScanner::scan(const ScanOptions &options, const std::atomic_bool &cancelled)
|
||||||
|
{
|
||||||
|
QList<Match> matches;
|
||||||
|
|
||||||
|
QList<QByteArray> envNeedles;
|
||||||
|
if (!options.envKeys.isEmpty() && !options.envValue.isEmpty()) {
|
||||||
|
envNeedles.reserve(options.envKeys.size());
|
||||||
|
for (const QString &key : options.envKeys) {
|
||||||
|
if (!key.isEmpty()) {
|
||||||
|
envNeedles.push_back((key + QLatin1Char('=') + options.envValue).toUtf8());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray cmdNeedle = options.cmdlineSubstring.isEmpty() ? QByteArray{} : options.cmdlineSubstring.toUtf8();
|
||||||
|
const bool checkExePrefix = !options.exePathPrefix.isEmpty();
|
||||||
|
|
||||||
|
const QList<uint> pids = listPids();
|
||||||
|
for (uint pid : pids) {
|
||||||
|
if (cancelled.load()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString pidDir = QStringLiteral("/proc/%1").arg(pid);
|
||||||
|
|
||||||
|
bool envMatched = false;
|
||||||
|
if (!envNeedles.isEmpty()) {
|
||||||
|
const QByteArray env = readEnviron(pidDir);
|
||||||
|
if (!env.isEmpty()) {
|
||||||
|
for (const QByteArray &needle : envNeedles) {
|
||||||
|
if (containsNullSeparatedEntry(env, needle)) {
|
||||||
|
envMatched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cmdlineMatched = false;
|
||||||
|
QByteArray cmd;
|
||||||
|
if (!cmdNeedle.isEmpty()) {
|
||||||
|
cmd = readCmdline(pidDir);
|
||||||
|
cmdlineMatched = !cmd.isEmpty() && cmd.contains(cmdNeedle);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool exeMatched = false;
|
||||||
|
QString exe;
|
||||||
|
if (checkExePrefix) {
|
||||||
|
exe = readExeLink(pidDir);
|
||||||
|
exeMatched = !exe.isEmpty() && exe.startsWith(options.exePathPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!envMatched && !cmdlineMatched && !exeMatched) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Match m;
|
||||||
|
m.pid = pid;
|
||||||
|
m.envMatched = envMatched;
|
||||||
|
m.cmdlineMatched = cmdlineMatched;
|
||||||
|
m.exeMatched = exeMatched;
|
||||||
|
|
||||||
|
if (exe.isEmpty()) {
|
||||||
|
exe = readExeLink(pidDir);
|
||||||
|
}
|
||||||
|
m.exe = exe;
|
||||||
|
|
||||||
|
if (cmd.isEmpty()) {
|
||||||
|
cmd = readCmdline(pidDir);
|
||||||
|
}
|
||||||
|
m.cmdline = QString::fromLocal8Bit(cmd).replace(QLatin1Char('\0'), QLatin1Char(' ')).trimmed();
|
||||||
|
|
||||||
|
matches.push_back(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.preferEnvironmentMatches) {
|
||||||
|
bool anyEnvMatch = false;
|
||||||
|
for (const Match &m : std::as_const(matches)) {
|
||||||
|
if (m.envMatched) {
|
||||||
|
anyEnvMatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anyEnvMatch) {
|
||||||
|
QList<Match> filtered;
|
||||||
|
filtered.reserve(matches.size());
|
||||||
|
for (const Match &m : std::as_const(matches)) {
|
||||||
|
if (m.envMatched) {
|
||||||
|
filtered.push_back(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
static QByteArray readProcFile(const QString &path, qint64 maxSize = 65536)
|
static QByteArray readProcFile(const QString &path, qint64 maxSize = 65536)
|
||||||
{
|
{
|
||||||
QFile f(path);
|
QFile f(path);
|
||||||
|
|
@ -136,141 +263,75 @@ static QList<uint> listPids()
|
||||||
|
|
||||||
QList<ProcessScanner::Match> ProcessScanner::findByEnvironment(const QString &key, const QString &value)
|
QList<ProcessScanner::Match> ProcessScanner::findByEnvironment(const QString &key, const QString &value)
|
||||||
{
|
{
|
||||||
QList<Match> matches;
|
ScanOptions opts;
|
||||||
const QByteArray needle = (key + QLatin1Char('=') + value).toUtf8();
|
opts.envKeys = {key};
|
||||||
|
opts.envValue = value;
|
||||||
const QList<uint> pids = listPids();
|
return scan(opts);
|
||||||
for (uint pid : pids) {
|
|
||||||
const QString pidDir = QStringLiteral("/proc/%1").arg(pid);
|
|
||||||
const QByteArray env = readEnviron(pidDir);
|
|
||||||
if (env.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (containsNullSeparatedEntry(env, needle)) {
|
|
||||||
Match m;
|
|
||||||
m.pid = pid;
|
|
||||||
m.exe = readExeLink(pidDir);
|
|
||||||
const QByteArray cmd = readCmdline(pidDir);
|
|
||||||
m.cmdline = QString::fromLocal8Bit(cmd).replace(QLatin1Char('\0'), QLatin1Char(' ')).trimmed();
|
|
||||||
matches.push_back(m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matches;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<ProcessScanner::Match> ProcessScanner::findByAnyEnvironment(const QStringList &keys, const QString &value)
|
QList<ProcessScanner::Match> ProcessScanner::findByAnyEnvironment(const QStringList &keys, const QString &value)
|
||||||
{
|
{
|
||||||
QList<Match> matches;
|
ScanOptions opts;
|
||||||
if (keys.isEmpty()) {
|
opts.envKeys = keys;
|
||||||
return matches;
|
opts.envValue = value;
|
||||||
}
|
return scan(opts);
|
||||||
|
|
||||||
QList<QByteArray> needles;
|
|
||||||
needles.reserve(keys.size());
|
|
||||||
for (const QString &key : keys) {
|
|
||||||
if (!key.isEmpty()) {
|
|
||||||
needles.push_back((key + QLatin1Char('=') + value).toUtf8());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (needles.isEmpty()) {
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QList<uint> pids = listPids();
|
|
||||||
for (uint pid : pids) {
|
|
||||||
const QString pidDir = QStringLiteral("/proc/%1").arg(pid);
|
|
||||||
const QByteArray env = readEnviron(pidDir);
|
|
||||||
if (env.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool found = false;
|
|
||||||
for (const QByteArray &needle : needles) {
|
|
||||||
if (containsNullSeparatedEntry(env, needle)) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Match m;
|
|
||||||
m.pid = pid;
|
|
||||||
m.exe = readExeLink(pidDir);
|
|
||||||
const QByteArray cmd = readCmdline(pidDir);
|
|
||||||
m.cmdline = QString::fromLocal8Bit(cmd).replace(QLatin1Char('\0'), QLatin1Char(' ')).trimmed();
|
|
||||||
matches.push_back(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
return matches;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<ProcessScanner::Match> ProcessScanner::findByCmdline(const QString &substring)
|
QList<ProcessScanner::Match> ProcessScanner::findByCmdline(const QString &substring)
|
||||||
{
|
{
|
||||||
QList<Match> matches;
|
ScanOptions opts;
|
||||||
const QByteArray needle = substring.toUtf8();
|
opts.cmdlineSubstring = substring;
|
||||||
|
return scan(opts);
|
||||||
const QList<uint> pids = listPids();
|
|
||||||
for (uint pid : pids) {
|
|
||||||
const QString pidDir = QStringLiteral("/proc/%1").arg(pid);
|
|
||||||
const QByteArray cmd = readCmdline(pidDir);
|
|
||||||
if (cmd.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// cmdline has null-separated args; search the whole blob
|
|
||||||
if (cmd.contains(needle)) {
|
|
||||||
Match m;
|
|
||||||
m.pid = pid;
|
|
||||||
m.exe = readExeLink(pidDir);
|
|
||||||
m.cmdline = QString::fromLocal8Bit(cmd).replace(QLatin1Char('\0'), QLatin1Char(' ')).trimmed();
|
|
||||||
matches.push_back(m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matches;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<ProcessScanner::Match> ProcessScanner::findByExePath(const QString &dirPrefix)
|
QList<ProcessScanner::Match> ProcessScanner::findByExePath(const QString &dirPrefix)
|
||||||
{
|
{
|
||||||
QList<Match> matches;
|
ScanOptions opts;
|
||||||
|
opts.exePathPrefix = dirPrefix;
|
||||||
const QList<uint> pids = listPids();
|
return scan(opts);
|
||||||
for (uint pid : pids) {
|
|
||||||
const QString pidDir = QStringLiteral("/proc/%1").arg(pid);
|
|
||||||
const QString exe = readExeLink(pidDir);
|
|
||||||
if (exe.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (exe.startsWith(dirPrefix)) {
|
|
||||||
Match m;
|
|
||||||
m.pid = pid;
|
|
||||||
m.exe = exe;
|
|
||||||
const QByteArray cmd = readCmdline(pidDir);
|
|
||||||
m.cmdline = QString::fromLocal8Bit(cmd).replace(QLatin1Char('\0'), QLatin1Char(' ')).trimmed();
|
|
||||||
matches.push_back(m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matches;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProcessScanner::pollUntilFound(std::function<QList<Match>()> matcher, int intervalMs, int timeoutMs)
|
void ProcessScanner::pollUntilFound(Matcher matcher, int intervalMs, int timeoutMs)
|
||||||
{
|
{
|
||||||
cancel();
|
cancel();
|
||||||
|
m_cancelToken = std::make_shared<std::atomic_bool>(false);
|
||||||
m_matcher = std::move(matcher);
|
m_matcher = std::move(matcher);
|
||||||
|
|
||||||
|
if (!m_matcher) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_deadline.start(timeoutMs);
|
m_deadline.start(timeoutMs);
|
||||||
m_timer.start(intervalMs);
|
m_timer.start(intervalMs);
|
||||||
|
|
||||||
startScan();
|
startScan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProcessScanner::pollUntilFound(std::function<QList<Match>()> matcher, int intervalMs, int timeoutMs)
|
||||||
|
{
|
||||||
|
pollUntilFound(
|
||||||
|
[matcher = std::move(matcher)](const std::atomic_bool &cancelled) {
|
||||||
|
if (cancelled.load()) {
|
||||||
|
return QList<Match>{};
|
||||||
|
}
|
||||||
|
if (!matcher) {
|
||||||
|
return QList<Match>{};
|
||||||
|
}
|
||||||
|
return matcher();
|
||||||
|
},
|
||||||
|
intervalMs,
|
||||||
|
timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
void ProcessScanner::cancel()
|
void ProcessScanner::cancel()
|
||||||
{
|
{
|
||||||
m_timer.stop();
|
m_timer.stop();
|
||||||
m_deadline.stop();
|
m_deadline.stop();
|
||||||
m_matcher = nullptr;
|
if (m_cancelToken) {
|
||||||
|
m_cancelToken->store(true);
|
||||||
|
}
|
||||||
|
m_cancelToken.reset();
|
||||||
|
m_matcher = {};
|
||||||
++m_generation;
|
++m_generation;
|
||||||
m_scanInFlight = false;
|
m_scanInFlight = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,16 @@
|
||||||
|
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QLoggingCategory>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
#include <atomic>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(lcProcessScanner)
|
||||||
|
|
||||||
class ProcessScanner : public QObject
|
class ProcessScanner : public QObject
|
||||||
{
|
{
|
||||||
|
|
@ -18,12 +23,28 @@ class ProcessScanner : public QObject
|
||||||
public:
|
public:
|
||||||
explicit ProcessScanner(QObject *parent = nullptr);
|
explicit ProcessScanner(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
struct ScanOptions {
|
||||||
|
QStringList envKeys;
|
||||||
|
QString envValue;
|
||||||
|
QString cmdlineSubstring;
|
||||||
|
QString exePathPrefix;
|
||||||
|
bool preferEnvironmentMatches = false;
|
||||||
|
};
|
||||||
|
|
||||||
struct Match {
|
struct Match {
|
||||||
uint pid = 0;
|
uint pid = 0;
|
||||||
QString exe;
|
QString exe;
|
||||||
QString cmdline;
|
QString cmdline;
|
||||||
|
bool envMatched = false;
|
||||||
|
bool cmdlineMatched = false;
|
||||||
|
bool exeMatched = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using Matcher = std::function<QList<Match>(const std::atomic_bool &cancelled)>;
|
||||||
|
|
||||||
|
static QList<Match> scan(const ScanOptions &options);
|
||||||
|
static QList<Match> scan(const ScanOptions &options, const std::atomic_bool &cancelled);
|
||||||
|
|
||||||
// Find PIDs whose /proc/<pid>/environ contains key=value
|
// Find PIDs whose /proc/<pid>/environ contains key=value
|
||||||
static QList<Match> findByEnvironment(const QString &key, const QString &value);
|
static QList<Match> findByEnvironment(const QString &key, const QString &value);
|
||||||
|
|
||||||
|
|
@ -38,6 +59,7 @@ public:
|
||||||
|
|
||||||
// Async poll: calls matcher repeatedly until it returns non-empty or timeout.
|
// Async poll: calls matcher repeatedly until it returns non-empty or timeout.
|
||||||
// Emits found() with matching PIDs, or timedOut() on failure.
|
// Emits found() with matching PIDs, or timedOut() on failure.
|
||||||
|
void pollUntilFound(Matcher matcher, int intervalMs = 500, int timeoutMs = 15000);
|
||||||
void pollUntilFound(std::function<QList<Match>()> matcher, int intervalMs = 500, int timeoutMs = 15000);
|
void pollUntilFound(std::function<QList<Match>()> matcher, int intervalMs = 500, int timeoutMs = 15000);
|
||||||
|
|
||||||
void cancel();
|
void cancel();
|
||||||
|
|
@ -51,7 +73,8 @@ private:
|
||||||
|
|
||||||
QTimer m_timer;
|
QTimer m_timer;
|
||||||
QTimer m_deadline;
|
QTimer m_deadline;
|
||||||
std::function<QList<Match>()> m_matcher;
|
Matcher m_matcher;
|
||||||
|
std::shared_ptr<std::atomic_bool> m_cancelToken;
|
||||||
quint64 m_generation = 0;
|
quint64 m_generation = 0;
|
||||||
bool m_scanInFlight = false;
|
bool m_scanInFlight = false;
|
||||||
quint64 m_scanGeneration = 0;
|
quint64 m_scanGeneration = 0;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue