From 69ca6efdf1acdcd740afc06f9589a975a377a377 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Wed, 20 May 2026 19:31:45 +0200 Subject: [PATCH] process: derive AppID from /proc/$pid/cgroup instead of walking the process tree Replace the old get_appid_from_pid(), which walked up the process tree looking for a "reaper" process and parsing its command line, with a simpler and more robust approach that reads /proc/$pid/cgroup and extracts the AppID from the Steam systemd scope name (app-steam-app${appid}-${reaperpid}.scope). Move the implementation to Utils/Process, and add unit tests. --- .github/workflows/main.yml | 2 +- src/Backends/OpenVRBackend.cpp | 5 +- src/Utils/Process.cpp | 47 +++++++++++++++- src/Utils/Process.h | 8 ++- src/steamcompmgr.cpp | 99 +--------------------------------- src/wlserver.cpp | 6 +-- tests/meson.build | 20 +++++-- tests/test_utils_process.cpp | 44 +++++++++++++++ 8 files changed, 117 insertions(+), 114 deletions(-) create mode 100644 tests/test_utils_process.cpp diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 48c8b7734e..a66da176c4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -44,7 +44,7 @@ jobs: export CC=gcc CXX=g++ meson configure -Db_coverage=true build-gcc meson test -C build-gcc --suite gamescope -v - gcovr -f src --xml -o coverage.xml -s build-gcc + gcovr -f src -e src/reshade --xml -o coverage.xml -s build-gcc - name: Upload coverage report uses: actions/upload-artifact@v7 with: diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index efdc779ce2..c29d32210d 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -20,6 +20,7 @@ #include "edid.h" #include "Ratio.h" #include "LibInputHandler.h" +#include "Utils/Process.h" #include #include @@ -124,8 +125,6 @@ namespace vr extern std::atomic g_unCurrentVRSceneAppId; -uint32_t get_appid_from_pid( pid_t pid ); - /////////////////////////////////////////////// // Josh: // GetVulkanInstanceExtensionsRequired and GetVulkanDeviceExtensionsRequired return *space separated* exts :( @@ -1179,7 +1178,7 @@ namespace gamescope if (m_uCurrentScenePid != vrEvent.data.process.pid) { m_uCurrentScenePid = vrEvent.data.process.pid; - m_uCurrentSceneAppId = get_appid_from_pid(m_uCurrentScenePid); + m_uCurrentSceneAppId = gamescope::Process::GetAppIdFromPid(m_uCurrentScenePid); g_unCurrentVRSceneAppId = m_uCurrentSceneAppId; openvr_log.debugf("SceneApplicationChanged -> pid: %u appid: %u", m_uCurrentScenePid, m_uCurrentSceneAppId); diff --git a/src/Utils/Process.cpp b/src/Utils/Process.cpp index f5191af8ea..fdafd6692b 100644 --- a/src/Utils/Process.cpp +++ b/src/Utils/Process.cpp @@ -1,11 +1,15 @@ #include "Process.h" -#include "../Utils/Algorithm.h" +#include "Algorithm.h" #include "../convar.h" #include "../log.hpp" #include "../Utils/Defer.h" -#include #include +#include +#include +#include +#include +#include #include #include @@ -538,4 +542,43 @@ namespace gamescope::Process return __progname; } + uint32_t GetAppIdFromCgroup( std::istream &stream ) + { + std::string line; + while ( std::getline( stream, line ) ) + { + // cgroup line format: hierarchy-ID:controller-list:cgroup-path + size_t first_colon = line.find( ':' ); + if ( first_colon == std::string::npos ) + continue; + size_t second_colon = line.find( ':', first_colon + 1 ); + if ( second_colon == std::string::npos ) + continue; + + const char *path = line.c_str() + second_colon + 1; + const char *last_slash = strrchr( path, '/' ); + const char *scope = last_slash ? last_slash + 1 : path; + + pid_t reaperpid = 0; + uint32_t appid = 0; + if ( sscanf( scope, "app-steam-app%u-%d.scope", &appid, &reaperpid ) == 2 && appid != 0 ) + return appid; + } + return 0; + } + + uint32_t GetAppIdFromPid( pid_t pid ) + { + char filename[256]; + snprintf( filename, sizeof( filename ), "/proc/%i/cgroup", pid ); + std::ifstream cgroup_file( filename ); + + if ( !cgroup_file.is_open() || cgroup_file.bad() ) + return 0; + + uint32_t appid = GetAppIdFromCgroup( cgroup_file ); + if ( appid != 0 ) + s_ProcessLog.debugf( "AppID %u derived from cgroup for pid %d", appid, pid ); + return appid; + } } diff --git a/src/Utils/Process.h b/src/Utils/Process.h index 24c87d302c..d074a34811 100644 --- a/src/Utils/Process.h +++ b/src/Utils/Process.h @@ -1,7 +1,9 @@ #pragma once -#include +#include #include +#include +#include #include #include @@ -43,4 +45,6 @@ namespace gamescope::Process const char *GetProcessName(); -} \ No newline at end of file + uint32_t GetAppIdFromCgroup( std::istream &stream ); + uint32_t GetAppIdFromPid( pid_t pid ); +} diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index b64d4597dd..c30937113b 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -43,15 +43,11 @@ #include #include #include -#include #include #include -#include #include #include -#include #include -#include #include #include @@ -97,7 +93,6 @@ #include "reshade_effect_manager.hpp" #include "BufferMemo.h" #include "Utils/Process.h" -#include "Utils/Algorithm.h" #include "wlr_begin.hpp" #include "wlr/types/wlr_pointer_constraints_v1.h" @@ -4932,98 +4927,6 @@ get_name_from_pid( pid_t pid ) return procNameStr; } -uint32_t -get_appid_from_pid( pid_t pid ) -{ - uint32_t unFoundAppId = 0; - - char filename[256]; - pid_t next_pid = pid; - - while ( 1 ) - { - snprintf( filename, sizeof( filename ), "/proc/%i/stat", next_pid ); - std::ifstream proc_stat_file( filename ); - - if (!proc_stat_file.is_open() || proc_stat_file.bad()) - break; - - std::string proc_stat; - - std::getline( proc_stat_file, proc_stat ); - - char *procName = nullptr; - char *lastParens = nullptr; - - for ( uint32_t i = 0; i < proc_stat.length(); i++ ) - { - if ( procName == nullptr && proc_stat[ i ] == '(' ) - { - procName = &proc_stat[ i + 1 ]; - } - - if ( proc_stat[ i ] == ')' ) - { - lastParens = &proc_stat[ i ]; - } - } - - if (!lastParens) - break; - - *lastParens = '\0'; - char state; - int parent_pid = -1; - - sscanf( lastParens + 1, " %c %d", &state, &parent_pid ); - - if ( strcmp( "reaper", procName ) == 0 ) - { - snprintf( filename, sizeof( filename ), "/proc/%i/cmdline", next_pid ); - std::ifstream proc_cmdline_file( filename ); - std::string proc_cmdline; - - bool bSteamLaunch = false; - uint32_t unAppId = 0; - - std::getline( proc_cmdline_file, proc_cmdline ); - - for ( uint32_t j = 0; j < proc_cmdline.length(); j++ ) - { - if ( proc_cmdline[ j ] == '\0' && j + 1 < proc_cmdline.length() ) - { - if ( strcmp( "SteamLaunch", &proc_cmdline[ j + 1 ] ) == 0 ) - { - bSteamLaunch = true; - } - else if ( sscanf( &proc_cmdline[ j + 1 ], "AppId=%u", &unAppId ) == 1 && unAppId != 0 ) - { - if ( bSteamLaunch == true ) - { - unFoundAppId = unAppId; - } - } - else if ( strcmp( "--", &proc_cmdline[ j + 1 ] ) == 0 ) - { - break; - } - } - } - } - - if ( parent_pid == -1 || parent_pid == 0 ) - { - break; - } - else - { - next_pid = parent_pid; - } - } - - return unFoundAppId; -} - static pid_t get_win_pid(xwayland_ctx_t *ctx, Window id) { @@ -5113,7 +5016,7 @@ add_win(xwayland_ctx_t *ctx, Window id, Window prev, unsigned long sequence) { if ( new_win->pid != -1 ) { - new_win->appID = get_appid_from_pid( new_win->pid ); + new_win->appID = gamescope::Process::GetAppIdFromPid( new_win->pid ); } else { diff --git a/src/wlserver.cpp b/src/wlserver.cpp index 2675583d15..d00b2fc706 100644 --- a/src/wlserver.cpp +++ b/src/wlserver.cpp @@ -66,7 +66,7 @@ #include "InputEmulation.h" #include "commit.h" #include "Timeline.h" -#include "Utils/NonCopyable.h" +#include "Utils/Process.h" #if HAVE_PIPEWIRE #include "pipewire.hpp" @@ -1845,8 +1845,6 @@ void xdg_toplevel_new(struct wl_listener *listener, void *data) { } -uint32_t get_appid_from_pid( pid_t pid ); - wlserver_xdg_surface_info* waylandy_type_surface_new(struct wl_client *client, struct wlr_surface *surface) { wlserver_wl_surface_info *wlserver_surface = get_wl_surface_info(surface); @@ -1868,7 +1866,7 @@ wlserver_xdg_surface_info* waylandy_type_surface_new(struct wl_client *client, s { pid_t nPid = 0; wl_client_get_credentials( client, &nPid, nullptr, nullptr ); - window->appID = get_appid_from_pid( nPid ); + window->appID = gamescope::Process::GetAppIdFromPid( nPid ); } window->_window_types.emplace(); diff --git a/tests/meson.build b/tests/meson.build index c74aef1baf..ee8f912101 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,16 +1,28 @@ catch2_dep = dependency('catch2-with-main', required: true) srcdir = '../src' -unittest_src = [] + +convar_unittest_src = [] foreach src : gamescope_core_src + ['Script/Script.cpp', 'convar_script.cpp'] - unittest_src += srcdir / src + convar_unittest_src += srcdir / src endforeach - test_convar = executable( 'test_convar', - unittest_src + ['test_convar.cpp'], + convar_unittest_src + ['test_convar.cpp'], include_directories: [srcdir, sol2_include], dependencies: [catch2_dep, luajit_dep, cap_dep, drm_dep, glm_dep, wlroots_dep], cpp_args: gamescope_cpp_args, ) test('convar', test_convar) + +utils_process_unittest_src = [] +foreach src : gamescope_core_src + utils_process_unittest_src += srcdir / src +endforeach +test_utils_process = executable( + 'test_utils_process', + utils_process_unittest_src + ['test_utils_process.cpp'], + include_directories: [srcdir], + dependencies: [catch2_dep, cap_dep], +) +test('utils/process', test_utils_process) diff --git a/tests/test_utils_process.cpp b/tests/test_utils_process.cpp new file mode 100644 index 0000000000..cd40094947 --- /dev/null +++ b/tests/test_utils_process.cpp @@ -0,0 +1,44 @@ +#include + +#include + +#include "Utils/Process.h" + +using namespace gamescope; + +TEST_CASE("GetAppIdFromCgroup", "[appid]") { + SECTION("steam app scope") { + std::istringstream stream("0::/user.slice/user-1000.slice/user@1000.service/app.slice/app-steam-app12345-9876.scope\n"); + REQUIRE(Process::GetAppIdFromCgroup(stream) == 12345u); + } + + SECTION("no matching scope") { + std::istringstream stream("0::/user.slice/user-1000.slice/session.scope\n"); + REQUIRE(Process::GetAppIdFromCgroup(stream) == 0u); + } + + SECTION("multiple lines, one matching") { + std::istringstream stream( + "12:devices:/user.slice\n" + "11:memory:/user.slice/user-1000.slice/session-1.scope\n" + "0::/user.slice/user-1000.slice/user@1000.service/app.slice/app-steam-app567-42.scope\n" + ); + REQUIRE(Process::GetAppIdFromCgroup(stream) == 567u); + } + + SECTION("empty stream") { + std::istringstream stream(""); + REQUIRE(Process::GetAppIdFromCgroup(stream) == 0u); + } + + SECTION("malformed lines") { + std::istringstream stream("not-a-valid-cgroup-line\n"); + REQUIRE(Process::GetAppIdFromCgroup(stream) == 0u); + + stream = std::istringstream("not-a-valid:cgroup-line\n"); + REQUIRE(Process::GetAppIdFromCgroup(stream) == 0u); + + stream = std::istringstream("not-a-valid:cgroup-line:\n"); + REQUIRE(Process::GetAppIdFromCgroup(stream) == 0u); + } +}