mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 17:00:41 +02:00
Dropping a game's .zip, folder or .exe on Send to Frame now adds it to the headset's Steam library through Valve's SteamOS Devkit title path, with the runtime picked from the program's header: Windows PE -> Proton Experimental (steam_play=1), aarch64 ELF -> SteamLinuxRuntime_4-arm64, x86-64 ELF -> SteamLinuxRuntime_4 (through FEX). Other architectures are refused. - frame/devkit-utils: Valve's devkit-utils vendored unmodified (MIT, steamos-devkit v0.20260925.1), synced to ~/devkit-utils by stamp, bundled in the app and compiled in CI. - ui/frame_titles.py: inspect (safe unzip, ELF/PE classification, launch target ranking), install(path, name=None, exe=None, runtime=None, progress=None), list, launch, remove, plus a CLI. - ui/server.py: /api/titles (inspect/install/discard/launch/remove), /api/titles/job progress, and an upload mode 'title'. - ui/index.html: confirm dialog (name, launch target, runtime), install progress, and a Sideloaded titles list with Launch and Remove. The app's preload passes a dropped folder's path. - tests and docs/sideloading.md. Device-side behaviour is inferred from Valve's source; the headset was offline, so none of it has been checked on a Frame yet. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
88 lines
3.8 KiB
Python
88 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import sys
|
|
import os
|
|
import logging
|
|
from urllib.parse import quote_plus as urllib_quote_plus
|
|
import json
|
|
import tempfile
|
|
|
|
from . import validate_steam_client
|
|
from . import execute_steam_client_command
|
|
from . import wait_on_file_response
|
|
|
|
import logging as logging_module
|
|
logger = logging_module.getLogger(__name__)
|
|
|
|
|
|
def resolve_shortcuts():
|
|
# make sure there is a steam client online that we can talk to before doing anything
|
|
validate_steam_client()
|
|
|
|
# scan the devkit games
|
|
installed_gameids = set([])
|
|
devkit_game_path = os.path.expanduser('~/devkit-game')
|
|
if not os.path.exists(devkit_game_path):
|
|
logger.info('%r does not exist, creating', devkit_game_path)
|
|
os.mkdir(devkit_game_path)
|
|
entries = sorted(os.scandir(devkit_game_path), key=lambda entry: entry.name)
|
|
directories = [e for e in entries if e.is_dir()]
|
|
for d in directories:
|
|
gameid = d.name
|
|
file_names = [f.name for f in entries if f.is_file() and f.name.startswith(gameid)]
|
|
has_argv = '{0}-argv.json'.format(gameid) in file_names
|
|
has_settings = '{0}-settings.json'.format(gameid) in file_names
|
|
if (not has_argv and not has_settings):
|
|
logger.info('Subfolder %r in %r is not accompanied by devkit configuration files, ignoring', d.name, devkit_game_path)
|
|
continue
|
|
logger.info('Found installed Devkit Game: %r', gameid)
|
|
installed_gameids.add(gameid)
|
|
|
|
# ask the Steam Client which Devkit Games are registered
|
|
with tempfile.TemporaryDirectory(prefix='list-shortcuts') as tempdir:
|
|
response = os.path.join(tempdir, 'shortcuts.json')
|
|
cmd = 'list-shortcuts?response={}'.format(
|
|
urllib_quote_plus(os.path.join(response))
|
|
)
|
|
# send the request
|
|
execute_steam_client_command(cmd)
|
|
with wait_on_file_response(response) as response:
|
|
client_shortcuts = json.loads(response)
|
|
logger.debug(client_shortcuts)
|
|
assert client_shortcuts['version'] == 2
|
|
registered_gameids = set([])
|
|
logger.info('Steam Client has %d registered devkit game(s)', len(client_shortcuts['gameids']))
|
|
for gameid in client_shortcuts['gameids']:
|
|
logger.info('Found Devkit Game registered with Steam Client: %r', gameid)
|
|
registered_gameids.add(gameid)
|
|
|
|
# any registered game that is not found installed on disk needs to be removed
|
|
for remove_gameid in registered_gameids - installed_gameids:
|
|
with tempfile.TemporaryDirectory(prefix='delete-shortcut') as tempdir:
|
|
logger.info('Removing stale registered Devkit Game: %r', remove_gameid)
|
|
response = os.path.join(tempdir, 'shortcut-deleted')
|
|
cmd = 'delete-shortcut?response={}&gameid={}'.format(
|
|
urllib_quote_plus(response),
|
|
remove_gameid
|
|
)
|
|
execute_steam_client_command(cmd)
|
|
with wait_on_file_response(response) as response:
|
|
logger.info('from Steam Client: %s', response.strip())
|
|
|
|
# any installed game that is not found registered needs to be added
|
|
for add_gameid in installed_gameids - registered_gameids:
|
|
with tempfile.TemporaryDirectory(prefix='create-shortcut') as tempdir:
|
|
logger.info('Registering installed Dekit Game: %r', add_gameid)
|
|
response = os.path.join(tempdir, 'registered')
|
|
cmd = 'create-shortcut?response={}&gameid={}&directory={}'.format(
|
|
urllib_quote_plus(response),
|
|
add_gameid,
|
|
urllib_quote_plus(devkit_game_path)
|
|
)
|
|
execute_steam_client_command(cmd)
|
|
with wait_on_file_response(response) as response:
|
|
logger.info('from Steam Client: %s', response.strip())
|
|
|
|
if __name__ == '__main__':
|
|
resolve_shortcuts()
|