Module steamback.util

Expand source code
import psutil
import re
import asyncio
import os
from typing import NamedTuple
from . import Engine

"""Create a game info object: contains game_id and install_root
"""


def make_game_info(p: Engine, game_id: int) -> dict:
    # we try to use the version in the engine if possible - because it has fully validated info
    info = p.all_games.get(game_id, None)

    if not info:
        print(f'Warning: no info found for { game_id } - simulating...')
        info = {
            # On a real steamdeck there may be multiple install_roots (main vs sdcard etc) (but only one per game)
            "install_root": p.get_steam_root(),
            "game_id": game_id,
            "game_name": None
        }

    return info


"""Find running steam games and return their game IDs (only used on linux desktops - not used in decky)

Look for processes with names like this to find running steam games.  Remove any duplicates.
home.../.steam/debian-installation/ubuntu12_32/reaper SteamLaunch AppId=1318690 ...
"""


def find_running_games() -> list[int]:
    # also available in environ['SteamGameId']
    appMatch = re.compile('AppId=(.+)')
    # steam username is in environ['SteamAppUser']

    """Get the game id from a process, or None if process is not a game"""
    def get_game_id(p: psutil.Process) -> int:
        line = p.cmdline()
        if len(line) >= 3 and line[1] == "SteamLaunch":
            # environ = p.environ()
            match = appMatch.fullmatch(line[2])
            if match:
                return int(match.group(1))
        return None

    r = []
    for proc in psutil.process_iter():
        try:
            id = get_game_id(proc)
            if id:
                r.append(id)
        except psutil.AccessDenied:
            pass  # Windows won't let us see some processes
    return r


class CheckResult(NamedTuple):
    game_started: bool  # true if there is a game just started
    backed_up: list[dict]  # the games we just backed up (probably only 0 or 1)


"""Watch steam and allow async polling for game exit
"""


class SteamWatcher:
    def __init__(self, engine: Engine):
        self.was_running = set()
        self.engine = engine

    """Look for any game exits and return the saveinfo for any backups performed
    """
    async def check_once(self) -> CheckResult:
        running = set(find_running_games())

        # set of games that just started
        started = running - self.was_running

        # set of games that just stopped
        stopped = self.was_running - running

        backups = []
        for game_id in stopped:
            info = make_game_info(self.engine, game_id)
            b = await self.engine.do_backup(info)
            if b is not None:
                backups.append(b)

        # get ready for next time
        self.was_running = running
        return CheckResult(game_started=len(started) > 0, backed_up=backups)

    async def run_forever(self):
        self.engine.logger.info(
            "Watching Steam for game exit, press Ctrl-C to quit...")
        while True:
            await asyncio.sleep(5)
            await self.check_once()

Functions

def find_running_games() ‑> list[int]
Expand source code
def find_running_games() -> list[int]:
    # also available in environ['SteamGameId']
    appMatch = re.compile('AppId=(.+)')
    # steam username is in environ['SteamAppUser']

    """Get the game id from a process, or None if process is not a game"""
    def get_game_id(p: psutil.Process) -> int:
        line = p.cmdline()
        if len(line) >= 3 and line[1] == "SteamLaunch":
            # environ = p.environ()
            match = appMatch.fullmatch(line[2])
            if match:
                return int(match.group(1))
        return None

    r = []
    for proc in psutil.process_iter():
        try:
            id = get_game_id(proc)
            if id:
                r.append(id)
        except psutil.AccessDenied:
            pass  # Windows won't let us see some processes
    return r
def make_game_info(p: Engine, game_id: int) ‑> dict
Expand source code
def make_game_info(p: Engine, game_id: int) -> dict:
    # we try to use the version in the engine if possible - because it has fully validated info
    info = p.all_games.get(game_id, None)

    if not info:
        print(f'Warning: no info found for { game_id } - simulating...')
        info = {
            # On a real steamdeck there may be multiple install_roots (main vs sdcard etc) (but only one per game)
            "install_root": p.get_steam_root(),
            "game_id": game_id,
            "game_name": None
        }

    return info

Classes

class CheckResult (game_started: bool, backed_up: list[dict])

CheckResult(game_started, backed_up)

Expand source code
class CheckResult(NamedTuple):
    game_started: bool  # true if there is a game just started
    backed_up: list[dict]  # the games we just backed up (probably only 0 or 1)

Ancestors

  • builtins.tuple

Instance variables

var backed_up : list[dict]

Alias for field number 1

var game_started : bool

Alias for field number 0

class SteamWatcher (engine: Engine)
Expand source code
class SteamWatcher:
    def __init__(self, engine: Engine):
        self.was_running = set()
        self.engine = engine

    """Look for any game exits and return the saveinfo for any backups performed
    """
    async def check_once(self) -> CheckResult:
        running = set(find_running_games())

        # set of games that just started
        started = running - self.was_running

        # set of games that just stopped
        stopped = self.was_running - running

        backups = []
        for game_id in stopped:
            info = make_game_info(self.engine, game_id)
            b = await self.engine.do_backup(info)
            if b is not None:
                backups.append(b)

        # get ready for next time
        self.was_running = running
        return CheckResult(game_started=len(started) > 0, backed_up=backups)

    async def run_forever(self):
        self.engine.logger.info(
            "Watching Steam for game exit, press Ctrl-C to quit...")
        while True:
            await asyncio.sleep(5)
            await self.check_once()

Methods

async def check_once(self) ‑> CheckResult
Expand source code
async def check_once(self) -> CheckResult:
    running = set(find_running_games())

    # set of games that just started
    started = running - self.was_running

    # set of games that just stopped
    stopped = self.was_running - running

    backups = []
    for game_id in stopped:
        info = make_game_info(self.engine, game_id)
        b = await self.engine.do_backup(info)
        if b is not None:
            backups.append(b)

    # get ready for next time
    self.was_running = running
    return CheckResult(game_started=len(started) > 0, backed_up=backups)
async def run_forever(self)
Expand source code
async def run_forever(self):
    self.engine.logger.info(
        "Watching Steam for game exit, press Ctrl-C to quit...")
    while True:
        await asyncio.sleep(5)
        await self.check_once()