mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 12:00:37 +02:00
Package the Frame Control web UI as an installable Electron Mac app and bring in the tooling built alongside it. - app/: Electron wrapper that starts ui/server.py on a free loopback port, hardened window (sandbox, no navigation, runAsNode fuse off), login-shell PATH so Homebrew tools work from Finder, first-run offer to run connect.sh, ad-hoc signed DMG/zip via electron-builder. - ui/: headset view (OpenVR screenshots), device status, library, Steam "Get games" (owned games, install, store search), Android apps as persistent Lepton instances with a rated F-Droid catalogue and a private compatibility database, Android display controls over ADB, file and clipboard transfer, Flatpaks, remote and power actions. - apk-catalog/, compat-db/, frame/: catalogue build pipeline, Lakebed capsule for compatibility reports, Frame-side launchers. - tests/ and CI: server guard and validation tests plus Steam helper tests, run on Python 3.9 with script and app syntax checks. - Docs: README leads with the Mac app; new Android, panels, Steam games and field-notes docs; security notes on LAN-exposed ADB ports. Screenshot values for the headset's IP and Wi-Fi name are placeholders. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
32 lines
1.4 KiB
Python
32 lines
1.4 KiB
Python
import struct, urllib.request, zlib
|
|
UA={'User-Agent':'steam-frame-compat-scan/1.0'}
|
|
def rng(url, start, end=None):
|
|
h=dict(UA); h['Range']=f'bytes={start}-' if end is None else f'bytes={start}-{end}'
|
|
with urllib.request.urlopen(urllib.request.Request(url,headers=h),timeout=60) as r:
|
|
return r.read()
|
|
def tail(url, n):
|
|
h=dict(UA); h['Range']=f'bytes=-{n}'
|
|
with urllib.request.urlopen(urllib.request.Request(url,headers=h),timeout=60) as r:
|
|
return r.read()
|
|
def list_names(url, size):
|
|
t=tail(url, min(size, 65557))
|
|
i=t.rfind(b'PK\x05\x06')
|
|
if i<0: raise ValueError('no EOCD')
|
|
cd_size, cd_off = struct.unpack('<II', t[i+12:i+20])
|
|
base=size-len(t)
|
|
cd = t[cd_off-base:cd_off-base+cd_size] if cd_off>=base else rng(url, cd_off, cd_off+cd_size-1)
|
|
names=[]; p=0; entries={}
|
|
while p+46<=len(cd) and cd[p:p+4]==b'PK\x01\x02':
|
|
comp,=struct.unpack('<H',cd[p+10:p+12])
|
|
csize,usize=struct.unpack('<II',cd[p+20:p+28])
|
|
nl,el,cl=struct.unpack('<HHH',cd[p+28:p+34]); lho,=struct.unpack('<I',cd[p+42:p+46])
|
|
n=cd[p+46:p+46+nl].decode('utf-8','replace'); names.append(n); entries[n]=(comp,csize,lho)
|
|
p+=46+nl+el+cl
|
|
return names, entries, cd_size
|
|
def read_entry(url, entries, name):
|
|
comp,csize,lho=entries[name]
|
|
h=rng(url, lho, lho+29)
|
|
nl,el=struct.unpack('<HH',h[26:30])
|
|
data=rng(url, lho+30+nl+el, lho+30+nl+el+csize-1)
|
|
return zlib.decompress(data,-15) if comp==8 else data
|