mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-25 23:02:40 +01:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb3fd7cd4d | ||
|
|
74089fb2f1 | ||
|
|
7f828cbcf8 | ||
|
|
42665d14db |
No files matched your search
@@ -2,6 +2,17 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Compare two explicit Windows Wireless Adapter doctor reads in local memory.
|
||||
Highlight exact PnP instances and known state changes after a dropout;
|
||||
incomplete inventories stay unknown. A Forget button clears the comparison.
|
||||
No adapter setting changes or report uploads are made.
|
||||
|
||||
- Add recovery steps to the Windows Wireless Adapter doctor for streams that
|
||||
drop as they start and dongles that will not reconnect. Compare exact PnP IDs
|
||||
and test one connection condition at a time. In-app disable/re-enable remains
|
||||
deferred until an active Steam adapter instance can be identified safely.
|
||||
[Guide and limits](docs/windows-wireless-doctor.md).
|
||||
|
||||
- Add an explicit host window-switch button to whole-display panels: Alt-Tab
|
||||
on Windows and Command-Tab on macOS. Single-window panels keep their own
|
||||
source, so the button is hidden there.
|
||||
|
||||
@@ -25,6 +25,8 @@ See what the headset sees, install games and Android apps, move files and text a
|
||||
|
||||
<sub>Unofficial hobby project, not affiliated with Valve. Free and open source.</sub>
|
||||
|
||||
[See what was tested on a real Frame, in a VM, or only in automated checks](docs/current-support-matrix.md).
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "frame-control",
|
||||
"version": "0.4.24",
|
||||
"version": "0.4.25",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frame-control",
|
||||
"version": "0.4.24",
|
||||
"version": "0.4.25",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"electron": "^44.4.5",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "frame-control",
|
||||
"productName": "Frame Control",
|
||||
"version": "0.4.24",
|
||||
"version": "0.4.25",
|
||||
"description": "Desktop app for managing a Valve Steam Frame over SSH",
|
||||
"private": true,
|
||||
"main": "main.js",
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# What we have tested
|
||||
|
||||
Current for Frame Control **v0.4.25**, 25 October 2026. “Real Frame + physical PC” means an actual headset and a non-virtual computer. “VM only” means a Windows virtual machine, sometimes connected to the real Frame. A CI test or a simulated device is **not** a physical hardware test. The date/version column is the last relevant check, not a promise that every new release was retested on that platform. See each link for the exact procedure and limits.
|
||||
|
||||
| Feature | Real Frame + physical PC tested | VM only | Untested or limited | Last check and proof |
|
||||
|---|---|---|---|---|
|
||||
| Windows desktop in the headset | No physical Windows PC test | Windows 11 VM sent a whole desktop to a real Frame; this used an intermediate fix, not the final native DLL | Physical GPU, capture and controller input; individual windows unavailable in bundled DXGI build | 24 Oct, v0.4.10 installer + candidate fix: [VM log](evidence/windows-vm-2026-10-24.md), [PC guide](pc-in-headset.md) |
|
||||
| Linux desktop in the headset | No | No | Native Linux PC capture is disabled; lxso2 lacked a desktop portal and PipeWire session. The separate Steam `-pipewire` helper is not Frame Control's PC capture | 24 Oct, v0.4.17: [host audit](evidence/linux-pc-host-lxso2-2026-10-24.md), [Steam helper PR #163](https://github.com/saphid/frame-control/pull/163) |
|
||||
| Mac desktop in the headset | A physical Mac sent its screen/window to a real Frame | No | Current v0.4.25 Mac installer and worn-headset controller input have not been repeated | 28 Sep, pre-v0.4 / Frame OS 0.4.1: [Mac guide and measurement](streaming.md#b-show-the-macs-desktop-inside-the-frame) |
|
||||
| OBS headset source on Linux | Real Frame to OBS 30 on a Linux host; H.264 video and a synthetic 440 Hz output tone reached a saved recording | No | Real game audio, audible monitoring, perceptual A/V sync and awake headset view | 25 Oct, PR #174 head, shipped in v0.4.22: [OBS log](evidence/obs-media-source-real-2026-10-25.md) |
|
||||
| OBS headset source on Windows | No physical Windows PC test | No OBS/Frame VM end-to-end test | App Start/Stop path has automated Windows checks only | 25 Oct, v0.4.23: [Windows PR #179](https://github.com/saphid/frame-control/pull/179), [OBS guide](obs-headset-source.md) |
|
||||
| OBS headset source on macOS | No | No | Helper and OBS Media Source not tested on a Mac with a Frame | 25 Oct, v0.4.22: [OBS PR #174](https://github.com/saphid/frame-control/pull/174), [OBS guide](obs-headset-source.md) |
|
||||
| Wired connection doctor | No physical PC/Frame cable test of the doctor | No | Synthetic route/SSH tests; a separate Mac USB-C address observation does not test the doctor or game stream | 25 Oct, v0.4.23: [PR #178](https://github.com/saphid/frame-control/pull/178), [wired guide](wired-connection.md) |
|
||||
| Windows Wireless Adapter doctor | No physical Steam Wireless Adapter/dropout test | Windows 11 VM ran read-only checks without the adapter or WLAN | Adapter identification, dropouts and recovery on hardware | 24–25 Oct, v0.4.20–0.4.25: [VM log](evidence/windows-wireless-2026-10-24.md), [doctor guide](windows-wireless-doctor.md), [comparison PR #184](https://github.com/saphid/frame-control/pull/184) |
|
||||
| Linux Wireless Adapter doctor | No Steam Wireless Adapter hardware test | No | Read-only Linux host checks and mocked adapter states; no adapter-specific hardware observation | 24 Oct, v0.4.20: [PR #165](https://github.com/saphid/frame-control/pull/165), [doctor guide](linux-wireless-adapter-doctor.md) |
|
||||
| Switch PC windows | No physical Windows PC test | No real VM-to-Frame Alt+Tab check recorded | Explicit chord is covered by tests; actual window focus, keyboard layouts and controller interaction are unverified | 25 Oct, v0.4.24: [PR #181](https://github.com/saphid/frame-control/pull/181), [PC guide](pc-in-headset.md) |
|
||||
| Keyboard and trackpad from Frame Control | A Linux PC sent a click and ASCII keys to a temporary focused window on the real Frame | No | Worn-headset interaction, Unicode delivery and pixel-accurate pointing | 24 Oct, v0.4.19: [received-event log](evidence/pad-gamescope-2026-10-24.md), [PR #66](https://github.com/saphid/frame-control/pull/66) |
|
||||
| Backup and restore after reset | Same-headset backup, no-op save restore and Return to Game Mode checks | No | Restore **after a factory reset**, replacement restore and Android data restore have not been tested on a headset. No headset reset was done | 24 Oct, v0.4.17: [backup guide](backup-and-recovery.md), [Return to Game Mode log](evidence/return-game-mode-packaged-2026-10-24.json) |
|
||||
| Passthrough in recordings | No active-passthrough mirror check | No | Standby frame was uniform; whether visible room pixels enter `/dev/video99` is **unknown**. Raw cameras are never captured by Frame Control | 25 Oct, v0.4.21: [headset log](evidence/round3-headset-2026-10-25.md), [recording guide](recording.md#does-record-include-passthrough) |
|
||||
|
||||
Release notes must preserve these limits until a newer recorded check changes them. When a feature is retested, add its date, exact app/Frame version, host type and proof here. The older [v0.3.1 matrix](support-matrix.html) is a historical snapshot and does not describe current coverage.
|
||||
@@ -52,6 +52,7 @@
|
||||
<body>
|
||||
<main>
|
||||
<h1>Frame Control 0.3.1: features by OS</h1>
|
||||
<p><strong>This is a September 2026 archive.</strong> For current coverage and hardware limits, read the <a href="current-support-matrix.md">v0.4.25 support matrix</a>.</p>
|
||||
<p class="sub">Which features are built for each OS, and which were tested against a real Steam Frame
|
||||
(SteamOS 0.3.0, build 20260922.6101926). Status as of 26 September 2026, for
|
||||
<a href="https://github.com/saphid/steam-frame/pull/2">PR #2</a> (0.3.1). Every build now bundles its own
|
||||
|
||||
@@ -48,6 +48,46 @@ this report. A future disable feature needs a verified Steam adapter/session
|
||||
mapping, fresh protection for primary/active instances, explicit confirmation
|
||||
for one exact instance, and a tested recovery path that re-enables that same ID.
|
||||
|
||||
## When a stream drops immediately or the adapter will not reconnect
|
||||
|
||||
Read PC adapters **before** starting a stream, then refresh immediately after a
|
||||
drop. Compare the *exact* PnP instance IDs, presence and problem codes. Note
|
||||
whether the PC's own Wi-Fi drops too. A new `#2` name is not proof that this
|
||||
instance caused the failure. Keep the time of the drop and the two snapshots
|
||||
for Steam Support; the doctor does not save or upload them.
|
||||
|
||||
The app shows **Changes since previous read** after the second click. It
|
||||
compares exact PnP IDs, known status fields and reported channels/bands; an
|
||||
unknown value is never treated as a state change. If the PnP source was
|
||||
unavailable in either read, it declines to compare devices. Each successful
|
||||
read becomes the baseline for the next one. **Forget previous read** clears the
|
||||
baseline and comparison; closing the app clears them too. The before/after
|
||||
comparison stays in local memory and is not uploaded or added to telemetry.
|
||||
Reported changes can reflect sequential query timing or a service recovering,
|
||||
not a fault or permission to disable any instance.
|
||||
|
||||
To narrow a drop at stream start, try one condition at a time: wired PC
|
||||
networking instead of PC Wi-Fi, wired headphones instead of 2.4 GHz audio, and
|
||||
spacing the USB radio dongles apart. Recheck the stream after each change. The
|
||||
PC Wi-Fi channel in this report is not the Steam dongle's channel, so a matching
|
||||
or different number does not establish interference.
|
||||
|
||||
If the adapter will not reconnect, stop the failed stream and close Steam's
|
||||
streaming session before touching its USB connection. Refresh the inventory. If
|
||||
the dongle is absent, reconnect it once at the PC, understanding that this
|
||||
interrupts any connection using it, then refresh again. If the **same exact**
|
||||
PnP instance now reports problem code 22 (disabled), review that instance in
|
||||
Windows Device Manager and its normal enable action. Do not enable or disable a
|
||||
different numbered instance by guesswork. For a missing device or another
|
||||
problem code, record the before/after ID and code for Steam Support; do not
|
||||
guess a driver or change security settings.
|
||||
|
||||
These are user-controlled checks, not an automatic repair. We have no physical
|
||||
Steam Wireless Adapter dropout to reproduce and no verified mapping from a
|
||||
Wi-Fi Direct virtual instance to Steam's active session. An in-app disable and
|
||||
re-enable control remains deferred until that mapping and a recovery path are
|
||||
tested on real hardware.
|
||||
|
||||
## Checking a 2.4 GHz audio conflict
|
||||
|
||||
[Deko's report](https://x.com/DekoDrak/status/2113412044644897023)
|
||||
|
||||
@@ -212,7 +212,7 @@
|
||||
<div class="head">
|
||||
<span class="kicker">Download</span>
|
||||
<h2>Get Frame Control</h2>
|
||||
<p>Choose your computer’s OS and processor, not the headset’s. Python is bundled; Linux needs <code>ssh</code>, and arm64 Linux also needs your distribution’s <code>adb</code> for Android apps. <span hidden>Version <span data-version></span>.</span> <a href="https://github.com/saphid/frame-control/releases">Release notes</a></p>
|
||||
<p>Choose your computer’s OS and processor, not the headset’s. Python is bundled; Linux needs <code>ssh</code>, and arm64 Linux also needs your distribution’s <code>adb</code> for Android apps. <span hidden>Version <span data-version></span>.</span> <a href="https://github.com/saphid/frame-control/releases">Release notes</a> · <a href="https://github.com/saphid/frame-control/blob/main/docs/current-support-matrix.md">What we tested</a></p>
|
||||
</div>
|
||||
<div class="downloads">
|
||||
<div class="dl" id="download-mac" data-os="mac">
|
||||
|
||||
@@ -37,6 +37,33 @@ test('click reads once, disables repeated clicks and renders partial inventory',
|
||||
assert.match(f.el('wirelessResult').innerHTML,/distinct Wi-Fi Direct/);
|
||||
assert.match(f.el('wirelessStatus').textContent,/Partial PC snapshot/);
|
||||
});
|
||||
test('two explicit reads compare exact identities and known state changes without claiming a repair',async()=>{
|
||||
const f=fixture();
|
||||
const first={...sample,sources:{pnp:'ok'},devices:[{...sample.devices[0],id:'SWD\\WIFIDIRECT\\one',
|
||||
name:'Adapter <one>',present:true,problemCode:0}]};
|
||||
const second={...first,observedAt:'2026-10-24T12:01:00Z',devices:[
|
||||
{...first.devices[0],present:false,problemCode:22},
|
||||
{...first.devices[0],id:'SWD\\WIFIDIRECT\\two',name:'Adapter <two>'}]};
|
||||
let calls=0; f.context.api=async()=> ++calls===1 ? first : second;
|
||||
await f.el('wirelessRead').onclick();
|
||||
assert.match(f.el('wirelessComparison').innerHTML,/Read PC adapters again/);
|
||||
assert.equal(f.el('wirelessForget').disabled,false);
|
||||
await f.el('wirelessRead').onclick();
|
||||
const change=f.el('wirelessComparison').innerHTML;
|
||||
for(const phrase of ['Changes since previous read','Newly listed','presence Yes → No',
|
||||
'problem code 0 → 22','not a safe disable target','<one>','<two>']) assert.ok(change.includes(phrase),phrase);
|
||||
assert.doesNotMatch(change,/<one>|<two>/);
|
||||
f.el('wirelessForget').onclick();
|
||||
assert.equal(f.el('wirelessComparison').textContent,'');
|
||||
assert.equal(f.el('wirelessForget').disabled,true);
|
||||
});
|
||||
test('incomplete PnP reads cannot imply a device appeared or vanished',()=>{
|
||||
const f=fixture(); f.context.before={...sample,sources:{pnp:'unavailable'}};
|
||||
f.context.after={...sample,sources:{pnp:'ok'},devices:[]};
|
||||
const out=f.run('compareWireless(before,after)');
|
||||
assert.match(out,/comparison unavailable/);
|
||||
assert.doesNotMatch(out,/No longer listed|Newly listed/);
|
||||
});
|
||||
test('unsupported, busy and failed probes leave no stale inventory and allow retry',async()=>{
|
||||
for(const result of [{state:'unsupported',message:'Windows only'},{state:'busy',message:'Try again'},new Error('Denied')]) {
|
||||
const f=fixture();f.el('wirelessResult').textContent='stale inventory';
|
||||
|
||||
+52
-2
@@ -1441,8 +1441,9 @@
|
||||
<section class="panel" id="wireless" hidden>
|
||||
<h2>Wireless Adapter doctor</h2>
|
||||
<p class="sub">Read-only snapshot of this Windows PC. Works without a headset connection. Nothing on the Frame is read or changed.</p>
|
||||
<button class="action" id="wirelessRead">Read PC adapters</button>
|
||||
<div class="row"><button class="action" id="wirelessRead">Read PC adapters</button><button id="wirelessForget" disabled>Forget previous read</button></div>
|
||||
<p id="wirelessStatus" role="status" aria-live="polite">Choose Read PC adapters to start. No automatic polling.</p>
|
||||
<div id="wirelessComparison" aria-live="polite"></div>
|
||||
<div id="wirelessResult"></div>
|
||||
<div class="card" style="margin-top:16px"><h3>Before changing an adapter</h3>
|
||||
<p>Multiple Microsoft Wi-Fi Direct Virtual Adapter instances can be normal. A number such as #2, a disconnected link or a non-present device does not establish which instance is extra or safe to disable. This tool cannot reliably identify the Steam dongle's primary or active instance, so it offers no disable action.</p>
|
||||
@@ -1452,6 +1453,14 @@
|
||||
<p>A Razer or other 2.4 GHz audio dongle can share spectrum with Wi-Fi. This inventory cannot measure interference, detect the audio dongle's channel or prove what caused a dropout or PC shutdown.</p>
|
||||
<p>Try wired headphones briefly and compare. Space USB radio dongles apart with an extension lead; if your PC uses 2.4 GHz Wi-Fi, compare Ethernet or an existing 5/6 GHz network. Change one thing at a time and keep notes. No network or security settings are changed here.</p>
|
||||
</div>
|
||||
<div class="card" style="margin-top:16px"><h3>If streaming drops as it starts</h3>
|
||||
<p>Read PC adapters before starting, then refresh immediately after a drop. Compare exact PnP instance IDs, presence and problem codes; a new numbered name alone does not identify a fault. Note whether the PC's own Wi-Fi also drops.</p>
|
||||
<p>Retry once with wired PC networking or wired audio if available, then with the radio dongles spaced apart. Change one condition at a time. The PC Wi-Fi channel shown above is not the Steam dongle channel, and this check cannot prove RF interference.</p>
|
||||
</div>
|
||||
<div class="card" style="margin-top:16px"><h3>If the adapter will not reconnect</h3>
|
||||
<p>Stop the failed stream, close Steam's streaming session, and check that the adapter still appears in the refreshed PC snapshot. If it is absent, reconnect the USB dongle once at the PC and read again; this will interrupt any connection using it.</p>
|
||||
<p>If the same exact PnP instance shows problem code 22 (disabled), use Windows Device Manager to review that instance and its enable action. Do not enable or disable a different numbered instance by guesswork. If it stays missing or reports another problem code, keep the before/after IDs and error for Steam Support. Frame Control does not change the driver, radio or network settings.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel" id="powerReport">
|
||||
@@ -5662,7 +5671,34 @@ document.querySelectorAll('[data-power-format]').forEach(b => b.onclick = async
|
||||
|
||||
// ---- Read-only PC wireless doctor ----
|
||||
let wirelessSeq = 0;
|
||||
let wirelessPrevious = null; // memory only; never sent or saved
|
||||
const wirelessKnown = value => value === true ? 'Yes' : value === false ? 'No' : 'Unknown';
|
||||
function compareWireless(previous, current) {
|
||||
if (previous.sources?.pnp !== 'ok' || current.sources?.pnp !== 'ok')
|
||||
return '<p class="hint">Exact device comparison unavailable: the PnP inventory was incomplete in one read. The current snapshot is shown below.</p>';
|
||||
const keyed = snapshot => new Map((snapshot.devices || [])
|
||||
.filter(d => typeof d.id === 'string' && d.id)
|
||||
.map(d => [d.id.toLowerCase(), d]));
|
||||
const before = keyed(previous), after = keyed(current), changes = [];
|
||||
const describe = d => `${esc(d.name || 'Unnamed device')} (<code>${esc(d.id)}</code>)`;
|
||||
for (const [id, device] of after) {
|
||||
const old = before.get(id);
|
||||
if (!old) { changes.push(`Newly listed: ${describe(device)}`); continue; }
|
||||
for (const [field, label] of [['present','presence'], ['pnpStatus','PnP status'],
|
||||
['problemCode','problem code'], ['operational','operation'], ['media','media'],
|
||||
['defaultRoute','default route'], ['channel','reported channel'], ['band','reported band']]) {
|
||||
if (old[field] == null || device[field] == null || old[field] === device[field]) continue;
|
||||
const value = v => typeof v === 'boolean' ? wirelessKnown(v) : esc(v);
|
||||
changes.push(`${describe(device)}: ${label} ${value(old[field])} → ${value(device[field])}`);
|
||||
}
|
||||
}
|
||||
for (const [id, device] of before) if (!after.has(id)) changes.push(`No longer listed: ${describe(device)}`);
|
||||
const shown = changes.slice(0, 25);
|
||||
return `<div class="card"><h3>Changes since previous read</h3>
|
||||
<p class="sub">${esc(previous.observedAt || 'Earlier read')} → ${esc(current.observedAt || 'Current read')}. These are reported changes, not proof of a new fault or an unused adapter. A newly listed numbered Wi-Fi Direct instance is not a safe disable target.</p>
|
||||
${shown.length ? `<ul>${shown.map(line => `<li>${line}</li>`).join('')}</ul>` : '<p>No reported device changes in the available fields.</p>'}
|
||||
${changes.length > shown.length ? `<p>${changes.length - shown.length} more changes omitted; inspect the current snapshot below.</p>` : ''}</div>`;
|
||||
}
|
||||
function renderWireless(r) {
|
||||
const unknown = value => value == null ? 'Unknown' : esc(value);
|
||||
const states = (value, known) => known[value] || (value == null ? 'Unknown' : `Other (${esc(value)})`);
|
||||
@@ -5681,6 +5717,8 @@ function renderWireless(r) {
|
||||
$('wirelessRead').onclick = async () => {
|
||||
const seq = ++wirelessSeq;
|
||||
$('wirelessRead').disabled = true;
|
||||
$('wirelessForget').disabled = true;
|
||||
$('wirelessComparison').textContent = '';
|
||||
$('wirelessResult').textContent = '';
|
||||
$('wirelessStatus').textContent = 'Reading this PC’s adapters…';
|
||||
try {
|
||||
@@ -5690,14 +5728,26 @@ $('wirelessRead').onclick = async () => {
|
||||
$('wirelessStatus').textContent = r.message || 'PC wireless inventory unavailable.';
|
||||
return;
|
||||
}
|
||||
$('wirelessComparison').innerHTML = wirelessPrevious ? compareWireless(wirelessPrevious, r) :
|
||||
'<p class="hint">Read PC adapters again after a dropout to compare exact device reports. The previous read stays only in this open app.</p>';
|
||||
wirelessPrevious = r;
|
||||
$('wirelessResult').innerHTML = renderWireless(r);
|
||||
$('wirelessStatus').textContent = r.state === 'partial' ? 'Partial PC snapshot. Some sources are unavailable. No settings changed.' : 'PC snapshot received. Unknown channel/band fields may remain. No settings changed.';
|
||||
} catch (e) {
|
||||
if (seq === wirelessSeq) $('wirelessStatus').textContent = `PC wireless inventory unavailable: ${e.message}`;
|
||||
} finally {
|
||||
if (seq === wirelessSeq) $('wirelessRead').disabled = false;
|
||||
if (seq === wirelessSeq) {
|
||||
$('wirelessRead').disabled = false;
|
||||
$('wirelessForget').disabled = !wirelessPrevious;
|
||||
}
|
||||
}
|
||||
};
|
||||
$('wirelessForget').onclick = () => {
|
||||
wirelessPrevious = null;
|
||||
$('wirelessForget').disabled = true;
|
||||
$('wirelessComparison').textContent = '';
|
||||
$('wirelessStatus').textContent = 'Previous adapter read forgotten. Read again to start a new comparison.';
|
||||
};
|
||||
|
||||
// ---- Read-only game storage ----
|
||||
let storageSeq = 0;
|
||||
|
||||
Reference in new issue
Block a user