mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 19:11:08 +00:00
fix(session): wait for Steam readiness before launch (#2362)
* fix(session): wait for Steam readiness before launch * fix(session): keep the launch fallback when Steam readiness or the interstitial is unavailable * fix(session): cap the Steam readiness wait by elapsed time and bound each check * fix(session): keep waiting for Steam readiness through focus changes and never pass a zero timeout
This commit is contained in:
1 parent
836340f3a2
commit
6bcd431ca6
4 files changed
+97
-14
No files matched your search
@@ -927,6 +927,33 @@ def steam_shortcut_state(agent_dir, game_id, timeout=6):
|
||||
time.sleep(min(0.5, max(0, deadline - time.monotonic())))
|
||||
|
||||
|
||||
def steam_ready(agent_dir):
|
||||
expression = """(() => {
|
||||
const store = globalThis.appStore;
|
||||
return !!store?.m_bIsInitialized && typeof globalThis.SteamClient?.Apps?.ContinueGameAction === 'function';
|
||||
})()"""
|
||||
return cdp_eval(agent_dir, expression, await_promise=False, timeout=3).get("value") is True
|
||||
|
||||
|
||||
def steam_clear_pending_interstitial(agent_dir, app_id):
|
||||
if not isinstance(app_id, str) or not app_id.isascii() or not app_id.isdigit() or int(app_id) <= 0:
|
||||
raise AgentError("INVALID_APP_ID", "appId must be a positive decimal Steam app id")
|
||||
expression = """(async () => {
|
||||
const apps = globalThis.SteamClient?.Apps;
|
||||
if (typeof apps?.GetActiveGameActions !== 'function' || typeof apps.ContinueGameAction !== 'function') return false;
|
||||
const active = await apps.GetActiveGameActions() || [];
|
||||
for (const action of active) {
|
||||
const appid = String(action?.strAppID ?? action?.gameid ?? action?.nAppID ?? '');
|
||||
if (appid === %s && action?.strTaskName === 'ShowInterstitials') {
|
||||
apps.ContinueGameAction(action.nGameActionID, appid, 'ShowInterstitials');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})()""" % json.dumps(app_id)
|
||||
return cdp_eval(agent_dir, expression, timeout=3).get("value") is True
|
||||
|
||||
|
||||
def game_pids(app_id):
|
||||
wanted = [("SteamAppId=%s" % app_id).encode(), ("SteamGameId=%s" % app_id).encode()]
|
||||
me = os.getpid()
|
||||
@@ -1261,6 +1288,19 @@ def main(argv):
|
||||
return 1
|
||||
print(state)
|
||||
return {"ready": 0, "missing": 4, "not-shortcut": 3}.get(state, 1)
|
||||
if argv and argv[0] == "--steam-ready":
|
||||
try:
|
||||
return 0 if steam_ready(Path(base) / "agent") else 1
|
||||
except (AgentError, OSError, ValueError):
|
||||
return 1
|
||||
if argv and argv[0] == "--steam-clear-pending-interstitial":
|
||||
if len(argv) != 2:
|
||||
print("usage: droiddeck-agent --steam-clear-pending-interstitial <appid>", file=sys.stderr)
|
||||
return 2
|
||||
try:
|
||||
return 0 if steam_clear_pending_interstitial(Path(base) / "agent", argv[1]) else 1
|
||||
except (AgentError, OSError, ValueError):
|
||||
return 1
|
||||
serve(Path(base) / "agent", focus="--no-focus" not in argv)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -1244,7 +1244,14 @@ case $mode in
|
||||
sleep 1
|
||||
done
|
||||
[ "${#game}" -gt 10 ] && shortcut=1
|
||||
[ -n "$shortcut" ] || sleep 4
|
||||
_bl_ready_end=$((SECONDS + 30))
|
||||
while [ "$waited" -lt 30 ] && [ "$((_bl_ready_end - SECONDS))" -gt 0 ]; do
|
||||
timeout "$((_bl_ready_end - SECONDS))" /usr/local/bin/droiddeck-agent --steam-ready >/dev/null 2>&1 && break
|
||||
[ "$((_bl_ready_end - SECONDS))" -gt 1 ] || break
|
||||
sleep 1
|
||||
waited=$((waited + 1))
|
||||
done
|
||||
waited=0
|
||||
echo "== steam: interface up; launching the requested game $game"
|
||||
_bl_hand_off
|
||||
[ -n "$shortcut" ] || return 0
|
||||
@@ -1264,7 +1271,9 @@ case $mode in
|
||||
elif _bl_starting; then
|
||||
echo "== steam: the requested game started while waiting; not launching again"
|
||||
elif [ "$(_bl_focused)" = 769 ]; then
|
||||
echo "== steam: launching the requested game again $game (overview status $status)"
|
||||
echo "== steam: clearing the pending launch interstitial for $game (overview status $status)"
|
||||
/usr/local/bin/droiddeck-agent --steam-clear-pending-interstitial "$(((game >> 32) & 0xffffffff))" >/dev/null 2>&1 && return 0
|
||||
echo "== steam: launching the requested game again $game"
|
||||
_bl_hand_off
|
||||
else
|
||||
echo "== steam: the game took focus while waiting; not launching again"
|
||||
|
||||
@@ -457,6 +457,26 @@ class AgentTest(unittest.TestCase):
|
||||
self.assertEqual(1, agent.main(["--steam-shortcut-state", game_id]))
|
||||
self.assertEqual(2, agent.main(["--steam-shortcut-state"]))
|
||||
|
||||
def test_steam_ready_uses_steams_initialized_store(self):
|
||||
with patch.object(agent, "cdp_eval", return_value={"value": True}) as evaluate:
|
||||
self.assertTrue(agent.steam_ready(self.dir))
|
||||
self.assertIn("m_bIsInitialized", evaluate.call_args.args[1])
|
||||
with patch.object(agent, "cdp_eval", return_value={"value": False}):
|
||||
self.assertFalse(agent.steam_ready(self.dir))
|
||||
|
||||
def test_pending_interstitial_is_continued_by_steam(self):
|
||||
with patch.object(agent, "cdp_eval", return_value={"value": True}) as evaluate:
|
||||
self.assertTrue(agent.steam_clear_pending_interstitial(self.dir, "3551182975"))
|
||||
expression = evaluate.call_args.args[1]
|
||||
self.assertIn("ContinueGameAction", expression)
|
||||
self.assertIn("ShowInterstitials", expression)
|
||||
self.assertIn('"3551182975"', expression)
|
||||
self.assertIn("await apps.GetActiveGameActions()", expression)
|
||||
with patch.object(agent, "cdp_eval", return_value={"value": False}):
|
||||
self.assertFalse(agent.steam_clear_pending_interstitial(self.dir, "3551182975"))
|
||||
with self.assertRaises(agent.AgentError):
|
||||
agent.steam_clear_pending_interstitial(self.dir, "0")
|
||||
|
||||
def test_asleep_command_reports_steams_own_sleep_state(self):
|
||||
with patch.dict(os.environ, {"BL_LAUNCH_DIR": str(self.dir.parent)}):
|
||||
for value, code in (("yes", 0), ("no", 5), ("unknown", 1), (None, 1)):
|
||||
|
||||
@@ -41,6 +41,7 @@ class SteamColdLaunchTest(unittest.TestCase):
|
||||
"#!/bin/sh\n"
|
||||
"printf '%s\n' \"$@\" > \"$AGENT_ARGS\"\n"
|
||||
"n=$(cat \"$AGENT_COUNT\" 2>/dev/null || echo 0); n=$((n + 1)); echo $n > \"$AGENT_COUNT\"\n"
|
||||
"[ \"$1\" = --steam-ready ] && exit \"${READY_STATUS:-0}\"\n"
|
||||
"exit \"$AGENT_STATUS\"\n")
|
||||
agent.chmod(0o755)
|
||||
sleep = self.bin / "sleep"
|
||||
@@ -55,12 +56,13 @@ class SteamColdLaunchTest(unittest.TestCase):
|
||||
pgrep.chmod(0o755)
|
||||
self.block = LAUNCH_BLOCK.replace("/usr/local/bin/droiddeck-agent", str(agent))
|
||||
|
||||
def run_launch(self, target, focus="769", helper_status=0, running_after=999):
|
||||
def run_launch(self, target, focus="769", helper_status=0, running_after=999, ready_status=0):
|
||||
env = dict(os.environ, HOME=str(self.root), BL_LAUNCH_DIR=str(self.launch_dir),
|
||||
PATH=str(self.bin) + os.pathsep + os.environ["PATH"],
|
||||
AGENT_ARGS=str(self.agent_args), SLEEP_ARGS=str(self.sleep_args),
|
||||
AGENT_COUNT=str(self.agent_count), AGENT_STATUS=str(helper_status),
|
||||
FOCUS=focus, FOCUS_COUNT=str(self.focus_count), RUNNING_AFTER=str(running_after))
|
||||
FOCUS=focus, FOCUS_COUNT=str(self.focus_count), RUNNING_AFTER=str(running_after),
|
||||
READY_STATUS=str(ready_status))
|
||||
script = self.block + "\nprintf 'ARG:%s\n' \"$@\"\nwait\n"
|
||||
url = target if target.startswith("steam://") else "steam://rungameid/" + target
|
||||
result = subprocess.run(["bash", "-c", script, "cold-launch", url],
|
||||
@@ -72,28 +74,40 @@ class SteamColdLaunchTest(unittest.TestCase):
|
||||
return [l for l in result.stdout.splitlines() if "launching the requested game" in l]
|
||||
|
||||
def test_a_shortcut_that_takes_focus_is_handed_over_once_without_waiting_for_the_overview(self):
|
||||
result = self.run_launch(SHORTCUT, focus="769 769 4242")
|
||||
result = self.run_launch(SHORTCUT, focus="769 4242")
|
||||
self.assertEqual(SHORTCUT, (self.launch_dir / "steam-game").read_text().strip())
|
||||
self.assertEqual(1, len(self.hand_offs(result)), result.stdout)
|
||||
self.assertFalse(self.agent_args.exists(), result.stdout)
|
||||
self.assertEqual(["1"], self.sleep_args.read_text().split())
|
||||
self.assertEqual(["--steam-ready"], self.agent_args.read_text().splitlines())
|
||||
self.assertFalse(self.sleep_args.exists())
|
||||
|
||||
def test_a_shortcut_that_never_takes_focus_is_handed_over_again_after_the_overview_wait(self):
|
||||
result = self.run_launch(SHORTCUT, focus="769", helper_status=0)
|
||||
self.assertEqual(2, len(self.hand_offs(result)), result.stdout)
|
||||
self.assertIn("launching the requested game again", result.stdout)
|
||||
self.assertEqual(["--wait-steam-shortcut-overview", SHORTCUT, "45"], self.agent_args.read_text().splitlines())
|
||||
self.assertEqual(1, len(self.hand_offs(result)), result.stdout)
|
||||
self.assertIn("clearing the pending launch interstitial", result.stdout)
|
||||
self.assertEqual(["--steam-clear-pending-interstitial", "3551182975"], self.agent_args.read_text().splitlines())
|
||||
self.assertEqual(["1"] * 20, self.sleep_args.read_text().split())
|
||||
|
||||
def test_a_timed_out_overview_still_gets_one_more_hand_off(self):
|
||||
result = self.run_launch(SHORTCUT, focus="769", helper_status=1)
|
||||
self.assertEqual(2, len(self.hand_offs(result)), result.stdout)
|
||||
self.assertIn("overview status 1", result.stdout)
|
||||
self.assertIn("launching the requested game again", result.stdout)
|
||||
|
||||
def test_a_steam_that_never_reports_ready_still_gets_the_launch(self):
|
||||
result = self.run_launch("413150", ready_status=1)
|
||||
self.assertEqual("413150", (self.launch_dir / "steam-game").read_text().strip())
|
||||
self.assertEqual(1, len(self.hand_offs(result)), result.stdout)
|
||||
self.assertEqual(["1"] * 30, self.sleep_args.read_text().split())
|
||||
|
||||
def test_a_focus_change_does_not_end_the_readiness_wait(self):
|
||||
result = self.run_launch("413150", focus="769 4242", ready_status=1)
|
||||
self.assertEqual(1, len(self.hand_offs(result)), result.stdout)
|
||||
self.assertEqual(["1"] * 30, self.sleep_args.read_text().split())
|
||||
|
||||
def test_a_game_still_starting_without_a_window_is_not_launched_twice(self):
|
||||
result = self.run_launch(SHORTCUT, running_after=1)
|
||||
self.assertEqual(1, len(self.hand_offs(result)), result.stdout)
|
||||
self.assertFalse(self.agent_args.exists())
|
||||
self.assertIn("--steam-ready", self.agent_args.read_text())
|
||||
self.assertEqual(["-f", "reaper SteamLaunch AppId=3551182975( |$)"],
|
||||
(self.root / "pgrep-args").read_text().splitlines())
|
||||
self.assertIn("already starting", result.stdout)
|
||||
@@ -108,15 +122,15 @@ class SteamColdLaunchTest(unittest.TestCase):
|
||||
focus = " ".join(["769"] * 23) + " 4242"
|
||||
result = self.run_launch(SHORTCUT, focus=focus, helper_status=0)
|
||||
self.assertEqual(1, len(self.hand_offs(result)), result.stdout)
|
||||
self.assertIn("took focus while waiting", result.stdout)
|
||||
self.assertNotIn("clearing the pending launch interstitial", result.stdout)
|
||||
self.assertNotIn("launching the requested game again", result.stdout)
|
||||
|
||||
def test_native_app_id_keeps_the_short_delay_and_is_handed_over_once(self):
|
||||
result = self.run_launch("413150")
|
||||
self.assertEqual("413150", (self.launch_dir / "steam-game").read_text().strip())
|
||||
self.assertEqual(1, len(self.hand_offs(result)), result.stdout)
|
||||
self.assertEqual("4", self.sleep_args.read_text().strip())
|
||||
self.assertFalse(self.agent_args.exists())
|
||||
self.assertFalse(self.sleep_args.exists())
|
||||
self.assertIn("--steam-ready", self.agent_args.read_text())
|
||||
|
||||
def test_a_restart_for_a_game_uses_the_same_hand_off(self):
|
||||
loop = SOURCE[SOURCE.index(' if [ -s "$HOME/.bl-restart-game" ]; then'):SOURCE.index(' if [ -f "$HOME/.bl-restart-once" ]; then')]
|
||||
|
||||
Reference in new issue
Block a user