a-la-karte/src/gamecenter/processscanner.cpp
Marco Allegretti 017073c162 Add Game Center daemon target
Build and install the Game Center daemon as a DBus-activated\nuser service. This introduces the new src/gamecenter target and\nwires it into the main build.
2026-02-06 14:01:35 +01:00

183 lines
4.8 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
#include "processscanner.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
ProcessScanner::ProcessScanner(QObject *parent)
: QObject(parent)
{
m_timer.setSingleShot(false);
m_deadline.setSingleShot(true);
connect(&m_deadline, &QTimer::timeout, this, [this]() {
m_timer.stop();
Q_EMIT timedOut();
});
connect(&m_timer, &QTimer::timeout, this, [this]() {
if (!m_matcher) {
m_timer.stop();
return;
}
const QList<Match> results = m_matcher();
if (!results.isEmpty()) {
m_timer.stop();
m_deadline.stop();
Q_EMIT found(results);
}
});
}
static QByteArray readProcFile(const QString &path, qint64 maxSize = 65536)
{
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) {
return {};
}
return f.read(maxSize);
}
static QString readExeLink(const QString &pidDir)
{
return QFileInfo(pidDir + QStringLiteral("/exe")).symLinkTarget();
}
static QByteArray readCmdline(const QString &pidDir)
{
return readProcFile(pidDir + QStringLiteral("/cmdline"));
}
static QByteArray readEnviron(const QString &pidDir)
{
return readProcFile(pidDir + QStringLiteral("/environ"));
}
static QList<uint> listPids()
{
QList<uint> pids;
const QDir proc(QStringLiteral("/proc"));
const QStringList entries = proc.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const QString &entry : entries) {
bool ok = false;
const uint pid = entry.toUInt(&ok);
if (ok && pid > 1) {
pids.push_back(pid);
}
}
return pids;
}
QList<ProcessScanner::Match> ProcessScanner::findByEnvironment(const QString &key, const QString &value)
{
QList<Match> matches;
const QByteArray needle = (key + QLatin1Char('=') + value).toUtf8();
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;
}
// environ entries are separated by null bytes
bool found = false;
int start = 0;
while (start < env.size()) {
int end = env.indexOf('\0', start);
if (end < 0) {
end = env.size();
}
if (env.mid(start, end - start) == needle) {
found = true;
break;
}
start = end + 1;
}
if (found) {
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<Match> matches;
const QByteArray needle = substring.toUtf8();
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<Match> matches;
const QList<uint> pids = listPids();
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)
{
cancel();
m_matcher = std::move(matcher);
// Try once immediately
const QList<Match> immediate = m_matcher();
if (!immediate.isEmpty()) {
Q_EMIT found(immediate);
return;
}
m_deadline.start(timeoutMs);
m_timer.start(intervalMs);
}
void ProcessScanner::cancel()
{
m_timer.stop();
m_deadline.stop();
m_matcher = nullptr;
}