mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 03:21:08 +00:00
An opt-in second Steam client (Steam settings, Steam client: x86-64): Valve's x86-64 Linux client, unchanged, runs under FEX in its own home with its own game library and the same sign-in. Linux builds run directly, so titles such as Team Fortress 2 and Half-Life use their Linux versions; Windows games use DroidDeck-Proton as on arm64. The arm64 client stays the default. - droiddeck-steam-x64: FEX and the x86-64 rootfs from DroidDeck-Components (sha256-pinned), client install and sync from Valve's CDN, sign-in shared with the arm64 client, its web helper on the GPU (ANGLE GL over FEX's GL thunk, zink, Turnip), Proton wrappers for the arm64 client's tools, DroidDeck direct for Linux builds, SD-card library (x86-64/ beside the arm64 one), repair. - FEX 2610 built by build-fex.yml with thunks and three patches: an openat2 fallback without openat2 (proot), newer-clang thunkgen, MAP_32BIT exhaustion returning ENOMEM. libblsession resolves RESOLVE_IN_ROOT paths inside the root. - Session: x86-64 gating, shortcuts and compatibility tools per client, logs from the client that ran. - App: client setting with the FEX version, an x86-64 chip on the Games header, the active client's library, art, storage, downloads, repair, Offline Mode, Decky, desktop mode, optional VAC diagnostics; per-client state for art, the content log, repair and leftovers. - Strings in every locale. - Review fixes: the library cache is saved only by the newest scan; an abandoned session's Steam logs come from the client it recorded; distinct VAC console names; FEXInterpreter in CPU pinning; clearer VAC hint in pl, ru, uk, cs and bg.
138 lines
7.1 KiB
Python
138 lines
7.1 KiB
Python
"""Desktop Steam stays on KDE; explicit Big Picture still switches sessions."""
|
|
import fcntl
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import time
|
|
import unittest
|
|
|
|
BIN = Path(__file__).resolve().parents[1] / 'linuxfs/overlay/usr/local/bin'
|
|
|
|
|
|
class DesktopSteamLaunchTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.root = Path(self.temp.name)
|
|
self.session = self.root / 'session'
|
|
self.session.mkdir()
|
|
self.env = dict(os.environ, HOME=str(self.root), XDG_DATA_HOME=str(self.root / 'data'),
|
|
XDG_CACHE_HOME=str(self.root / 'cache'), BL_DESKTOP='1',
|
|
BL_LAUNCH_DIR=str(self.session))
|
|
supervisor = self.root / 'supervisor'
|
|
supervisor.write_text('#!/bin/bash\nprintf "%s\\n" "$BL_INSIDE" "$BL_STEAM_UI" "$DISPLAY" "$@"\n')
|
|
supervisor.chmod(0o755)
|
|
x64 = self.root / 'steam-x64'
|
|
x64.write_text('#!/bin/bash\nprintf "%s\\n" x64 "$@"\n')
|
|
x64.chmod(0o755)
|
|
self.launcher = self.root / 'launch'
|
|
self.launcher.write_text((BIN / 'droiddeck-steam-launch').read_text().replace(
|
|
'/usr/local/bin/droiddeck-session', str(supervisor)).replace(
|
|
'/usr/local/bin/droiddeck-steam-x64', str(x64)))
|
|
|
|
def launch(self, *args):
|
|
return subprocess.run(['bash', str(self.launcher), *args], env=self.env,
|
|
text=True, capture_output=True)
|
|
|
|
def test_desktop_keeps_display_and_does_not_request_session_switch(self):
|
|
self.env['DISPLAY'] = ':7'
|
|
result = self.launch('desktop')
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual(result.stdout.splitlines(), ['1', 'desktop', ':7', 'steam', 'steam://open/library'])
|
|
self.assertFalse((self.session / 'steam-launch').exists())
|
|
|
|
def test_desktop_game_url_goes_to_client(self):
|
|
result = self.launch('desktop', '501590')
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual(result.stdout.splitlines()[-1], 'steam://rungameid/501590')
|
|
self.assertFalse((self.session / 'steam-launch').exists())
|
|
|
|
def test_second_click_forwards_without_another_supervisor(self):
|
|
lock = self.root / 'cache/droiddeck/steam-desktop.lock'
|
|
lock.parent.mkdir(parents=True)
|
|
steam = self.root / 'data/Steam/steamrtarm64/steam'
|
|
steam.parent.mkdir(parents=True)
|
|
steam.write_text('#!/bin/bash\nprintf "%s\\n" "$@"\n')
|
|
steam.chmod(0o755)
|
|
(self.root / '.steam').mkdir()
|
|
os.mkfifo(self.root / '.steam/steam.pipe')
|
|
(self.root / '.steam/steam.pid').write_text(str(os.getpid()))
|
|
with lock.open('w') as handle:
|
|
fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
result = self.launch('desktop', 'steam://open/settings')
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual(result.stdout.splitlines(), ['-nobigpicture', '-no-cef-sandbox', '-cef-ozone-platform=x11',
|
|
'-cef-disable-gpu', 'steam://open/settings'])
|
|
self.assertFalse((self.session / 'steam-launch').exists())
|
|
|
|
def test_second_click_forwards_to_the_x86_64_client_in_its_own_home(self):
|
|
lock = self.root / 'cache/droiddeck/steam-desktop.lock'
|
|
lock.parent.mkdir(parents=True)
|
|
x64_home = self.root / 'x64'
|
|
(x64_home / '.steam').mkdir(parents=True)
|
|
os.mkfifo(x64_home / '.steam/steam.pipe')
|
|
(x64_home / '.steam/steam.pid').write_text(str(os.getpid()))
|
|
self.env.update(BL_STEAM_CLIENT_ARCH='x86_64', BL_X64_HOME=str(x64_home))
|
|
with lock.open('w') as handle:
|
|
fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
result = self.launch('desktop', '501590')
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual(result.stdout.splitlines(), ['x64', '--exec', '-nobigpicture', '-no-cef-sandbox',
|
|
'-cef-ozone-platform=x11', '-cef-disable-gpu',
|
|
'steam://rungameid/501590'])
|
|
|
|
def test_second_click_waits_for_steam_ipc_during_startup(self):
|
|
lock = self.root / 'cache/droiddeck/steam-desktop.lock'
|
|
lock.parent.mkdir(parents=True)
|
|
steam = self.root / 'data/Steam/steamrtarm64/steam'
|
|
steam.parent.mkdir(parents=True)
|
|
marker = self.root / 'forwarded'
|
|
steam.write_text(f'#!/bin/bash\ntouch "{marker}"\n')
|
|
steam.chmod(0o755)
|
|
with lock.open('w') as handle:
|
|
fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
process = subprocess.Popen(['bash', str(self.launcher), 'desktop'], env=self.env,
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
try:
|
|
time.sleep(0.7)
|
|
self.assertIsNone(process.poll())
|
|
self.assertFalse(marker.exists())
|
|
(self.root / '.steam').mkdir()
|
|
(self.root / '.steam/steam.pid').write_text(str(os.getpid()))
|
|
os.mkfifo(self.root / '.steam/steam.pipe')
|
|
_, stderr = process.communicate(timeout=3)
|
|
self.assertEqual(process.returncode, 0, stderr)
|
|
self.assertTrue(marker.exists())
|
|
finally:
|
|
if process.poll() is None:
|
|
process.kill()
|
|
process.communicate()
|
|
|
|
def test_big_picture_requests_explicit_handoff(self):
|
|
result = self.launch('bigpicture')
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual((self.session / 'steam-launch').read_text(), 'ui=bigpicture\nurl=\n')
|
|
|
|
def test_outside_desktop_preserves_session_request(self):
|
|
self.env.pop('BL_DESKTOP')
|
|
result = self.launch('desktop', 'steam://open/settings')
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual((self.session / 'steam-launch').read_text(),
|
|
'ui=desktop\nurl=steam://open/settings\n')
|
|
|
|
def test_invalid_target_cannot_trigger_a_handoff(self):
|
|
self.assertEqual(self.launch('desktop', 'bad').returncode, 64)
|
|
self.assertFalse((self.session / 'steam-launch').exists())
|
|
|
|
def test_cef_software_is_scoped_to_the_kde_client(self):
|
|
source = (BIN / 'droiddeck-session').read_text()
|
|
setup = source[source.index(' _bl_ui=(-gamepadui)'):source.index(' if [ "${BL_STEAM_UI:-}" != desktop ]; then')]
|
|
for desktop, ui, software in [('1', 'desktop', True), ('1', 'bigpicture', False), ('', 'desktop', False)]:
|
|
with self.subTest(desktop=desktop, ui=ui):
|
|
env = dict(self.env, BL_DESKTOP=desktop, BL_STEAM_UI=ui)
|
|
result = subprocess.check_output(['bash', '-c', setup + '\nprintf "%s\\n" "${_bl_cef[@]}"'],
|
|
env=env, text=True)
|
|
self.assertEqual('-cef-disable-gpu' in result, software)
|
|
self.assertEqual('-cef-use-angle=vulkan' in result, not software)
|