Skip to content

Commit

Permalink
Fix broken running Steam App detection on linux (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
FrogTheFrog authored Jun 25, 2023
1 parent 0639c69 commit 47b3aff
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 39 deletions.
2 changes: 2 additions & 0 deletions src/lib/os/linux/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ set(HEADERS
nativeresolutionhandler.h
registryfileparser.h
registryfilewatcher.h
steamprocesslistobserver.h
steamregistryobserver.h
waylandresolutionhandler.h
x11resolutionhandler.h
Expand All @@ -44,6 +45,7 @@ set(SOURCES
nativeresolutionhandler.cpp
registryfileparser.cpp
registryfilewatcher.cpp
steamprocesslistobserver.cpp
steamregistryobserver.cpp
waylandresolutionhandler.cpp
x11resolutionhandler.cpp
Expand Down
93 changes: 90 additions & 3 deletions src/lib/os/linux/nativeprocesshandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ namespace
{
uint getParentPid(uint pid)
{
const std::array<uint, 2> pids{pid, 0};

proc_t proc_info;
memset(&proc_info, 0, sizeof(proc_info));

// NOLINTNEXTLINE(*-vararg)
PROCTAB* proc = openproc(PROC_FILLSTATUS | PROC_PID, &pid);
PROCTAB* proc = openproc(PROC_FILLSTATUS | PROC_PID, pids.data());
const auto cleanup{qScopeGuard(
[&]()
{
Expand All @@ -42,6 +44,47 @@ uint getParentPid(uint pid)

//---------------------------------------------------------------------------------------------------------------------

QString getCmdline(uint pid)
{
const std::array<uint, 2> pids{pid, 0};

proc_t proc_info;
memset(&proc_info, 0, sizeof(proc_info));

// NOLINTNEXTLINE(*-vararg)
PROCTAB* proc = openproc(PROC_FILLCOM | PROC_PID, pids.data());
const auto cleanup{qScopeGuard(
[&]()
{
if (proc != nullptr)
{
closeproc(proc);
}
})};
if (readproc(proc, &proc_info) == nullptr)
{
return {};
}

auto* ptr_list{proc_info.cmdline};
if (ptr_list == nullptr)
{
return {};
}

QStringList cmdline;
while (*ptr_list != nullptr)
{
cmdline.append(*ptr_list);
// NOLINTNEXTLINE(*-pointer-arithmetic)
ptr_list++;
}

return cmdline.join(' ');
}

//---------------------------------------------------------------------------------------------------------------------

std::vector<uint> getPids()
{
const QDir proc_dir{"/proc"};
Expand Down Expand Up @@ -79,8 +122,8 @@ std::vector<uint> getParentPids(const std::vector<uint>& pids)

std::vector<uint> getRelatedPids(uint pid)
{
std::vector<uint> all_pids{getPids()};
std::vector<uint> parent_pids{getParentPids(all_pids)};
const std::vector<uint> all_pids{getPids()};
const std::vector<uint> parent_pids{getParentPids(all_pids)};

Q_ASSERT(all_pids.size() == parent_pids.size());
std::vector<uint> related_pids;
Expand Down Expand Up @@ -166,4 +209,48 @@ void NativeProcessHandler::terminate(uint pid) const
}
}
}

//---------------------------------------------------------------------------------------------------------------------

std::vector<uint> NativeProcessHandler::getChildrenPids(uint pid) const
{
const std::vector<uint> all_pids{getPids()};
if (std::find(std::begin(all_pids), std::end(all_pids), pid) == std::end(all_pids))
{
// Process does not exist, early exit.
return {};
}

const std::vector<uint> parent_pids{getParentPids(all_pids)};
Q_ASSERT(all_pids.size() == parent_pids.size());

std::vector<uint> children_pids;
for (std::size_t i = 0; i < all_pids.size(); ++i)
{
const uint parent_pid{parent_pids[i]};
if (parent_pid != pid)
{
continue;
}

const uint process_pid{all_pids[i]};
if (process_pid == pid)
{
// Is this even possible? To be your own parent?
continue;
}

children_pids.push_back(process_pid);
}

return children_pids;
}

//---------------------------------------------------------------------------------------------------------------------

// NOLINTNEXTLINE(*-to-static)
QString NativeProcessHandler::getCmdline(uint pid) const
{
return ::getCmdline(pid);
}
} // namespace os
3 changes: 3 additions & 0 deletions src/lib/os/linux/nativeprocesshandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@ class NativeProcessHandler : public NativeProcessHandlerInterface
QString getExecPath(uint pid) const override;
void close(uint pid) const override;
void terminate(uint pid) const override;

std::vector<uint> getChildrenPids(uint pid) const;
QString getCmdline(uint pid) const;
};
} // namespace os
97 changes: 97 additions & 0 deletions src/lib/os/linux/steamprocesslistobserver.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// header file include
#include "steamprocesslistobserver.h"

// system/Qt includes
#include <QRegularExpression>

// local includes
#include "nativeprocesshandler.h"

//---------------------------------------------------------------------------------------------------------------------

namespace os
{
SteamProcessListObserver::SteamProcessListObserver()
{
connect(&m_check_timer, &QTimer::timeout, this, &SteamProcessListObserver::slotCheckProcessList);

const int interval_ms{2000};
m_check_timer.setInterval(interval_ms);
m_check_timer.setSingleShot(true);
}

//---------------------------------------------------------------------------------------------------------------------

void SteamProcessListObserver::observePid(uint pid)
{
stopObserving();

if (pid != 0)
{
m_steam_pid = pid;
slotCheckProcessList();
}
}

//---------------------------------------------------------------------------------------------------------------------

void SteamProcessListObserver::stopObserving()
{
m_check_timer.stop();
m_steam_pid = 0;
}

//---------------------------------------------------------------------------------------------------------------------

const std::set<uint>& SteamProcessListObserver::getAppIds() const
{
return m_app_ids;
}

//---------------------------------------------------------------------------------------------------------------------

void SteamProcessListObserver::slotCheckProcessList()
{
std::set<uint> running_apps;

if (m_steam_pid != 0)
{
NativeProcessHandler proc_handler;

const auto pids{proc_handler.getChildrenPids(m_steam_pid)};
for (auto pid : pids)
{
static const QRegularExpression app_id_matcher{R"(AppId=([0-9]+))",
QRegularExpression::CaseInsensitiveOption};
const auto match{app_id_matcher.match(proc_handler.getCmdline(pid))};
if (!match.hasCaptured(1))
{
continue;
}

const QString captured{match.captured(1)};

bool success{false};
const auto result{captured.toUInt(&success)};

if (!success)
{
continue;
}

running_apps.insert(result);
}
}

if (running_apps != m_app_ids)
{
std::swap(running_apps, m_app_ids);
emit signalListChanged();
}

if (m_steam_pid != 0)
{
m_check_timer.start();
}
}
} // namespace os
36 changes: 36 additions & 0 deletions src/lib/os/linux/steamprocesslistobserver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma once

// system/Qt includes
#include <QTimer>
#include <set>

//---------------------------------------------------------------------------------------------------------------------

namespace os
{
class SteamProcessListObserver : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(SteamProcessListObserver)

public:
explicit SteamProcessListObserver();
~SteamProcessListObserver() override = default;

void observePid(uint pid);
void stopObserving();

const std::set<uint>& getAppIds() const;

signals:
void signalListChanged();

private slots:
void slotCheckProcessList();

private:
std::set<uint> m_app_ids;
QTimer m_check_timer;
uint m_steam_pid{0};
};
} // namespace os
48 changes: 28 additions & 20 deletions src/lib/os/linux/steamregistryobserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@
namespace
{
const QStringList PID_PATH{{"Registry", "HKLM", "Software", "Valve", "Steam", "SteamPID"}};
const QStringList GLOBAL_APP_ID_PATH{{"Registry", "HKCU", "Software", "Valve", "Steam", "RunningAppID"}};
const QStringList APPS_PATH{{"Registry", "HKCU", "Software", "Valve", "Steam", "apps"}};
const QStringList APP_RUNNING_PATH{{"Running"}};
const QStringList APP_UPDATING_PATH{{"Updating"}};

//---------------------------------------------------------------------------------------------------------------------

Expand Down Expand Up @@ -68,6 +64,8 @@ SteamRegistryObserver::SteamRegistryObserver()
: m_watcher{QDir::homePath() + "/.steam/registry.vdf"}
{
connect(&m_watcher, &RegistryFileWatcher::signalRegistryChanged, this, &SteamRegistryObserver::slotRegistryChanged);
connect(&m_process_list_observer, &SteamProcessListObserver::signalListChanged, this,
&SteamRegistryObserver::slotRegistryChanged);
connect(&m_observation_delay, &QTimer::timeout, this,
[this]()
{
Expand All @@ -85,13 +83,16 @@ SteamRegistryObserver::SteamRegistryObserver()
void SteamRegistryObserver::startAppObservation()
{
m_observation_delay.start();
m_process_list_observer.observePid(m_pid);
}

//---------------------------------------------------------------------------------------------------------------------

void SteamRegistryObserver::stopAppObservation()
{
m_observation_delay.stop();
m_process_list_observer.stopObserving();

m_is_observing_apps = false;
m_global_app_id = 0;
if (m_tracked_app_data)
Expand Down Expand Up @@ -122,12 +123,13 @@ void SteamRegistryObserver::slotRegistryChanged()
{
const auto& data{m_watcher.getData()};
const auto get_uint = [](const qint64* ptr) { return ptr == nullptr ? 0 : static_cast<uint>(*ptr); };
const auto get_bool = [](const qint64* ptr) { return ptr == nullptr ? false : *ptr != 0; };

const auto pid{get_uint(getEntry<qint64>(PID_PATH, data))};
if (pid != m_pid)
{
m_pid = pid;

m_process_list_observer.observePid(m_pid);
emit signalSteamPID(m_pid);
}

Expand All @@ -146,31 +148,37 @@ void SteamRegistryObserver::slotRegistryChanged()
return;
}

const auto global_app_id{get_uint(getEntry<qint64>(GLOBAL_APP_ID_PATH, data))};
if (global_app_id != m_global_app_id)
{
m_global_app_id = global_app_id;
emit signalGlobalAppId(m_global_app_id);
}
// Note: everything after here is a workaround, because Steam is no longer saving stuff to the registry
// https://github.com/ValveSoftware/steam-for-linux/issues/9672

const auto running_apps{m_process_list_observer.getAppIds()};
if (m_tracked_app_data)
{
const auto* app_data{getEntry<os::RegistryFileWatcher::NodeList>(
APPS_PATH + QStringList{QString::number(m_tracked_app_data->m_app_id)}, data)};
const bool tracked_app_is_running{running_apps.contains(m_tracked_app_data->m_app_id)};
const uint global_app_id{tracked_app_is_running ? m_tracked_app_data->m_app_id
: running_apps.empty() ? 0
: *running_apps.begin()};

const bool is_updating{app_data == nullptr ? false : get_bool(getEntry<qint64>(APP_UPDATING_PATH, *app_data))};
if (is_updating != m_tracked_app_data->m_is_updating)
if (global_app_id != m_global_app_id)
{
m_tracked_app_data->m_is_updating = is_updating;
emit signalTrackedAppIsRunning(m_tracked_app_data->m_is_updating);
m_global_app_id = global_app_id;
emit signalGlobalAppId(m_global_app_id);
}

const bool is_running{app_data == nullptr ? false : get_bool(getEntry<qint64>(APP_RUNNING_PATH, *app_data))};
if (is_running != m_tracked_app_data->m_is_running)
if (tracked_app_is_running != m_tracked_app_data->m_is_running)
{
m_tracked_app_data->m_is_running = is_running;
m_tracked_app_data->m_is_running = tracked_app_is_running;
emit signalTrackedAppIsRunning(m_tracked_app_data->m_is_running);
}
}
else
{
const uint global_app_id{running_apps.empty() ? 0 : *running_apps.begin()};
if (global_app_id != m_global_app_id)
{
m_global_app_id = global_app_id;
emit signalGlobalAppId(m_global_app_id);
}
}
}
} // namespace os
Loading

0 comments on commit 47b3aff

Please sign in to comment.