mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-26 22:04:22 +01:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5438e1d9d0 | ||
|
|
9418a340eb | ||
|
|
a8bf01d660 | ||
|
|
5e3f9559c7 | ||
|
|
a31b4a426c | ||
|
|
ff465accc5 | ||
|
|
d9c2cc3f27 | ||
|
|
910d847573 | ||
|
|
89f986b91c | ||
|
|
3ca8de985e |
No files matched your search
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "frame-control",
|
||||
"version": "0.4.47",
|
||||
"version": "0.4.48",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frame-control",
|
||||
"version": "0.4.47",
|
||||
"version": "0.4.48",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"electron": "^44.4.5",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "frame-control",
|
||||
"productName": "Frame Control",
|
||||
"version": "0.4.47",
|
||||
"version": "0.4.48",
|
||||
"description": "Desktop app for managing a Valve Steam Frame over SSH",
|
||||
"private": true,
|
||||
"main": "main.js",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Emuchievements ARM64 hash helper feasibility, 26 October 2026
|
||||
|
||||
The exact official Emuchievements 2.0.5 archive (store ID 28, SHA-256
|
||||
`98dcb443e81b9f9011cfe77c69eb659869c8e2df2df8b0a443df66cbfc2f5a89`)
|
||||
contains an x86_64 `Emuchievements/bin/hash`. Frame Control still blocks
|
||||
that archive on ARM64; this experiment did not install the plugin.
|
||||
|
||||
The upstream source commit
|
||||
[`8567e94befffc943434b2adb842487274815f8bb`](https://github.com/EmuDeck/Emuchievements/commit/8567e94befffc943434b2adb842487274815f8bb)
|
||||
declares version 2.0.5. Its `src/py/main.py` matches the published ZIP's
|
||||
`Emuchievements/main.py` byte for byte (SHA-256
|
||||
`d4e72f136c94e48919575937ec4190f28864cc259eb111ef47f0df99f5912645`).
|
||||
The pinned build dependencies at that commit are libarchive
|
||||
`b3be94ba4f36943745e64a6a0a3c21c2d1281d3c`, libchdr
|
||||
`a20e04c12c0c20048bf771da6df7aa2e51f5e0cb`, and rcheevos
|
||||
`85a3209398e576d79170f569fcddf277640f3f06`.
|
||||
|
||||
On a separate Linux host, the version-matched source and submodules built an
|
||||
x86_64 `hash` with CMake. It and the official x86_64 binary both returned
|
||||
`ff78ca6c21ac11504dbddbd5d4e7524a` for one synthetic `.rom` file. An
|
||||
isolated Debian cross-build using `gcc-aarch64-linux-gnu` and
|
||||
`g++-aarch64-linux-gnu` produced an AArch64 binary with SHA-256
|
||||
`9c7aedb80bdd64941aa5dd9b41f9bb8471411c812e04c7ee7d60486779d99997`.
|
||||
It links to `liblzma.so.5`, `libstdc++.so.6`, `libgcc_s.so.1`, and `libc.so.6`.
|
||||
|
||||
A real Frame test acquired `/tmp/frame-test.lock` atomically with a unique
|
||||
owner before reading the device. SteamVR activity was **3** (standby),
|
||||
proximity raw **3.0625**, and battery **100%**. The ARM64 helper ran against
|
||||
the same synthetic ROM in a private `/tmp` directory, exited 0, and returned
|
||||
the same hash. The owned directory and lock were removed. No normal service,
|
||||
power setting, Steam library, real ROM, or installed plugin was changed.
|
||||
The separate host's source/build directory was also removed after the test.
|
||||
|
||||
This proves only a single-file helper path. The official ZIP remains blocked
|
||||
because it still bundles x86_64 bytes. A release-ready port needs a packaged
|
||||
ARM64 helper with GPL-3.0 source and license compliance, format-specific hash
|
||||
coverage (including archives and CHD), a managed plugin transaction, actual
|
||||
Decky backend and frontend checks, and cleanup. RetroAchievements login,
|
||||
real ROM scanning, wearer-visible VR, and physical controller use remain
|
||||
unverified.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Music Control 1.1.7 bound-method compatibility, 26 October 2026
|
||||
|
||||
The official Decky store archive for ID 111, version 1.1.7, is
|
||||
`3f56a3f56034b277ea74a2c37cc84133b69e382aa6bcb0b43876776aac737ed9`
|
||||
(SHA-256). The pinned `tests/fixtures/framey-official/111.zip` matches that
|
||||
digest and includes the upstream license.
|
||||
|
||||
The unchanged backend fails before it reaches D-Bus: `sp_list_media_players`
|
||||
raises `TypeError: Plugin._get_dbus_env() takes 1 positional argument but 2
|
||||
were given`, and `sp_play` raises the corresponding `_sp_player_dbus` argument
|
||||
error. The source passes `self` explicitly to methods already bound by Python.
|
||||
|
||||
For this exact store ID, archive digest and folder, a managed install removes
|
||||
the redundant argument at the five affected call sites. It checks the number
|
||||
of occurrences before writing `main.py` and records
|
||||
`music-control-bound-methods` in the managed receipt. The original ZIP remains
|
||||
the verified download; the older ID 12 archive is installed without this
|
||||
change.
|
||||
|
||||
`test_exact_music_control_backend_calls_work_after_managed_install` installs
|
||||
the official ZIP through the production transaction script into a disposable
|
||||
home. It imports the patched backend and exercises player enumeration,
|
||||
selection, play/pause, next/previous, volume and playback-status calls against
|
||||
a private fake `dbus-send` executable that records the actual command
|
||||
arguments. It then removes the plugin through the receipt-bound transaction.
|
||||
The same test confirms ID 12 retains its original backend source. The focused
|
||||
store suite passed **23/23** tests locally. The pinned frontend/fake-Frame
|
||||
install/remove tests are part of the hosted gate.
|
||||
|
||||
This proves that the Python argument mismatch no longer prevents backend
|
||||
command dispatch in the isolated fixture. The fixture is not an MPRIS player.
|
||||
Actual Decky 3.3.0 ARM64 execution, live player discovery, media transport,
|
||||
volume effects, album-art routing, and wearer-visible VR controls have not
|
||||
been verified. Music Control remains **partial**.
|
||||
@@ -0,0 +1,50 @@
|
||||
# v0.4.48 candidate gate, 26 October 2026
|
||||
|
||||
This candidate contains the exact-archive Music Control 1.1.7 Python call
|
||||
repair from [#335](https://github.com/saphid/frame-control/pull/335). All
|
||||
**11** hosted checks passed on its head
|
||||
`ff465accc508e40158c590eebffd4aec924503ac`, including fake-Frame e2e,
|
||||
Windows, macOS, Linux, ARM64 host libraries and iOS. The catalog remains
|
||||
**4 works / 52 partial / 49 broken / 11 N/A** out of 116. Music Control is
|
||||
partial: an isolated D-Bus command fixture proves dispatch after the repair;
|
||||
a live player and actual Decky ARM64 backend were not tested.
|
||||
|
||||
## Locked real Frame smoke
|
||||
|
||||
At 15:36 UTC, source head `ff465accc508e40158c590eebffd4aec924503ac`
|
||||
ran `scripts/frame-smoke.sh` on SteamOS 0.5.6 build `20261022.6153909`.
|
||||
The runner acquired its own unique `/tmp/frame-test.lock` owner before
|
||||
reading device status. Preflight found SteamVR activity **3** (standby),
|
||||
proximity raw **3.0625**, and battery **99%**. Owned ARM64 and Windows/Proton
|
||||
titles installed, launched and were removed. The optional x86_64 title
|
||||
installed and was removed; its launch was **skipped** because Steam Linux
|
||||
Runtime 4 was absent. The result was **12 passed / 1 skipped / 0 failed**.
|
||||
Owned cleanup passed and the command exited 0, including lock release.
|
||||
A separate fresh locked read found battery **100%**, activity **3** and no
|
||||
`fc_smoke_*` title directories; it released only its own lock. The private
|
||||
runner result is `2026-10-26T153655Z-20261022.6153909.json`.
|
||||
|
||||
At 15:42 UTC, exact version-bumped commit
|
||||
`a31b4a426cf4204e45fe218e204a26328aa0b1c9` ran the same token-locked
|
||||
smoke. Preflight found activity **3**, proximity raw **3.0625** and battery
|
||||
**100%**. It passed **12 / 1 optional x86 launch skipped / 0 failed**.
|
||||
Cleanup passed and the command exited 0 after releasing its lock. The
|
||||
private runner result is `2026-10-26T154258Z-20261022.6153909.json`.
|
||||
A fresh separately locked read found battery **100%**, activity **3** and
|
||||
no test-title directories, then released that lock. Merging current main
|
||||
into the candidate created commit
|
||||
`a8bf01d660cd9abb8d2a724626a06a0e7e6c2a6a`; its tree before this
|
||||
evidence document was byte-identical to the tested version-bumped commit
|
||||
(`d45c2465ddfcf877fe2c8bc30420d4719da5035e`).
|
||||
|
||||
No wearer was confirmed. Neither smoke woke the headset or ran the
|
||||
interactive worn-check script. Official-plugin wearer visibility, physical
|
||||
controllers, VR keyboard and optical comfort remain unverified. The hourly
|
||||
shared-lock worn check remains enabled.
|
||||
|
||||
## Remaining publication gates
|
||||
|
||||
The release PR's exact-head CI and installer previews, post-merge main CI,
|
||||
tagged builds with all ten installers and `SHA256SUMS`, independent package
|
||||
checks, publisher dry run and public `update.json` readback are required
|
||||
before publication.
|
||||
@@ -29,7 +29,7 @@ A [Free Loader duplicate-entry follow-up](evidence/framey-free-loader-duplicates
|
||||
|
||||
The current published digests are pinned in the JSON; if the store changed, retain/download those exact archives for reproduction. Audit downloads permit larger ZIPs for inspection; production installer limits remain 50 MiB ZIP / 150 MiB unpacked / 30 MiB per member.
|
||||
|
||||
After the VR job released the shared lock, this audit acquired it and used a private temporary Decky 3.3.0 ARM64 user service. Read-only ProtonDB `get_setting` and Steam Status `get_settings` calls succeeded through the live Steam CEF websocket. The service and private base were removed and the lock released; normal managed services stayed inactive, battery was 100 → 99, and no power setting was changed. [Redacted results](evidence/framey-official-backend-2026-10-25.json) record exactly what ran. A [2026-10-26 follow-up](evidence/framey-events-backend-2026-10-26.json) verified default Notebook drawing/save/reopen across a real backend restart and Simple Timer start/cancel, countdown/completion events, a visible toast and persisted recents/subtle mode. Only private test settings were written. Simple Timer remains partial because its own expired-timer startup raises a missing `timer_task` error; sound/suspend are unsupported. A [local audio follow-up](evidence/framey-audio-backends-2026-10-26.md) exercised Volume Mixer, Volume Boost and Mixer through real Decky 3.3.0 amd64 backends on private silent virtual audio targets. Sliders, numeric volume entry, independent server readbacks and Mixer filter persistence across a runtime restart passed. An [ARM64 audio follow-up](evidence/framey-audio-arm64-2026-10-26.md) verified the unmodified official backends in actual Decky 3.3.0 on the Frame user audio session. Volume Mixer and Mixer now qualify as works for per-app volume: Chromium controls changed only a named silent stream, with independent Pulse readbacks. That run left Volume Boost partial because sink writes were denied. A [Volume Boost follow-up](evidence/framey-volume-boost-arm64-2026-10-26.md) now verifies linked/separate channel controls, re-link and reopen through the actual ARM64 backend. Independent Pulse reads confirmed 125%/150% gain writes while the default sink stayed muted; exact original volume/mute was restored. Volume Boost qualifies as works for sink controls. Audible output, Bluetooth playback and worn VR remain unverified. A [running-context follow-up](evidence/framey-running-context-2026-10-26.md) supplies detached running-app/main-app reads and relays actual Steam lifetime callbacks while a panel is open. Real Frame checks verified an empty list, registration/cleanup and the exact Discord Status ARM64 backend reporting no IPC connection. Nonempty games and transitions were synthetic fixtures; real game events, Discord activity and worn VR remain unverified. Discord Status moves to partial; no new works claim is made. A [library context follow-up](evidence/framey-library-context-2026-10-26.md) read 1,106 app overviews, 895 game IDs and the current public Steam ID from real Frame CEF under the shared lock. Local Chromium replayed this detached snapshot to render Deck Settings game selection, Steamback's empty save panel and PlayTime statistics/settings. Steamback and PlayTime used actual Decky 3.3.0 amd64 backends in an isolated container; their ARM64 backends and automatic game hooks remain unverified. Shotty's frontend now starts, but its published backend fails on missing `click`; it stays broken. A [chunked frontend follow-up](evidence/framey-deck-shelves-browser-2026-10-26.md) loaded Deck Shelves 2.0.0 through a managed JavaScript module route and rendered its settings in an opaque local Chromium panel. Its backend was denied; Steam Home shelves and worn VR remain unverified. A [local module lookup follow-up](evidence/framey-module-lookup-2026-10-26.md) supplies a finite registry of Framey-local controls and CSS. TabMaster QAM/docs now render; Steam library-tab editing remains unavailable. That pass exposed Junk-Store executable-script extraction failures; the follow-up below repairs them. SteamGridDB still requires Steam context-menu internals and DeckSP still fails its published MobX fallback. That pass tested TabMaster/Junk-Store in actual isolated amd64 Decky, without ARM64 or worn-VR coverage. A [script entrypoint follow-up](evidence/framey-script-entrypoints-2026-10-26.md) grants owner execute to shebang scripts and ZIP-declared executables without copying archive privilege/write bits. Junk-Store now passes static menu/backend calls in actual Decky 3.3.0 ARM64 and local Chromium Games/Epic navigation, back and reopen. It moves to partial: About still crashes on missing installed-app metadata, Legendary is absent, and login/game operations remain unverified. Existing installations require update/reinstall to repair permissions. A [read-only installed-metadata follow-up](evidence/framey-installed-context-2026-10-26.md) now repairs the About crash. The library snapshot copies only Steam-supplied installed booleans into detached metadata, preserving unknown values as undefined. Junk-Store About/Dependencies, Back and reopen render in local Chromium with actual Frame library data and the official ARM64 backend. It remains partial: dependency/login/game operations, RSS, the optional Line truncated
|
||||
After the VR job released the shared lock, this audit acquired it and used a private temporary Decky 3.3.0 ARM64 user service. Read-only ProtonDB `get_setting` and Steam Status `get_settings` calls succeeded through the live Steam CEF websocket. The service and private base were removed and the lock released; normal managed services stayed inactive, battery was 100 → 99, and no power setting was changed. [Redacted results](evidence/framey-official-backend-2026-10-25.json) record exactly what ran. A [2026-10-26 follow-up](evidence/framey-events-backend-2026-10-26.json) verified default Notebook drawing/save/reopen across a real backend restart and Simple Timer start/cancel, countdown/completion events, a visible toast and persisted recents/subtle mode. Only private test settings were written. Simple Timer remains partial because its own expired-timer startup raises a missing `timer_task` error; sound/suspend are unsupported. A [local audio follow-up](evidence/framey-audio-backends-2026-10-26.md) exercised Volume Mixer, Volume Boost and Mixer through real Decky 3.3.0 amd64 backends on private silent virtual audio targets. Sliders, numeric volume entry, independent server readbacks and Mixer filter persistence across a runtime restart passed. An [ARM64 audio follow-up](evidence/framey-audio-arm64-2026-10-26.md) verified the unmodified official backends in actual Decky 3.3.0 on the Frame user audio session. Volume Mixer and Mixer now qualify as works for per-app volume: Chromium controls changed only a named silent stream, with independent Pulse readbacks. That run left Volume Boost partial because sink writes were denied. A [Volume Boost follow-up](evidence/framey-volume-boost-arm64-2026-10-26.md) now verifies linked/separate channel controls, re-link and reopen through the actual ARM64 backend. Independent Pulse reads confirmed 125%/150% gain writes while the default sink stayed muted; exact original volume/mute was restored. Volume Boost qualifies as works for sink controls. Audible output, Bluetooth playback and worn VR remain unverified. A [running-context follow-up](evidence/framey-running-context-2026-10-26.md) supplies detached running-app/main-app reads and relays actual Steam lifetime callbacks while a panel is open. Real Frame checks verified an empty list, registration/cleanup and the exact Discord Status ARM64 backend reporting no IPC connection. Nonempty games and transitions were synthetic fixtures; real game events, Discord activity and worn VR remain unverified. Discord Status moves to partial; no new works claim is made. A [library context follow-up](evidence/framey-library-context-2026-10-26.md) read 1,106 app overviews, 895 game IDs and the current public Steam ID from real Frame CEF under the shared lock. Local Chromium replayed this detached snapshot to render Deck Settings game selection, Steamback's empty save panel and PlayTime statistics/settings. Steamback and PlayTime used actual Decky 3.3.0 amd64 backends in an isolated container; their ARM64 backends and automatic game hooks remain unverified. Shotty's frontend now starts, but its published backend fails on missing `click`; it stays broken. A [chunked frontend follow-up](evidence/framey-deck-shelves-browser-2026-10-26.md) loaded Deck Shelves 2.0.0 through a managed JavaScript module route and rendered its settings in an opaque local Chromium panel. Its backend was denied; Steam Home shelves and worn VR remain unverified. A [local module lookup follow-up](evidence/framey-module-lookup-2026-10-26.md) supplies a finite registry of Framey-local controls and CSS. TabMaster QAM/docs now render; Steam library-tab editing remains unavailable. That pass exposed Junk-Store executable-script extraction failures; the follow-up below repairs them. SteamGridDB still requires Steam context-menu internals and DeckSP still fails its published MobX fallback. That pass tested TabMaster/Junk-Store in actual isolated amd64 Decky, without ARM64 or worn-VR coverage. A [script entrypoint follow-up](evidence/framey-script-entrypoints-2026-10-26.md) grants owner execute to shebang scripts and ZIP-declared executables without copying archive privilege/write bits. Junk-Store now passes static menu/backend calls in actual Decky 3.3.0 ARM64 and local Chromium Games/Epic navigation, back and reopen. It moves to partial: About still crashes on missing installed-app metadata, Legendary is absent, and login/game operations remain unverified. Existing installations require update/reinstall to repair permissions. A [read-only installed-metadata follow-up](evidence/framey-installed-context-2026-10-26.md) now repairs the About crash. The library snapshot copies only Steam-supplied installed booleans into detached metadata, preserving unknown values as undefined. Junk-Store About/Dependencies, Back and reopen render in local Chromium with actual Frame library data and the official ARM64 backend. It remains partial: dependency/login/game operations, RSS, the optional Line truncated
|
||||
|
||||
## Every current plugin
|
||||
|
||||
@@ -119,7 +119,7 @@ The API column shows manifest / actual bundled frontend API. Many manifests say
|
||||
| 108: HueSync 3.8.0 | 1 / 2 | N/A | Not supported on Frame: Targets handheld RGB/LED controllers (AYANEO/Ally/MSI), not Frame hardware. | static archive, local browser, fake-Frame |
|
||||
| 109: MuraDeck 2.1.4 | 1 / 2 | N/A | Not supported on Frame: Calibration is specifically for defective Samsung Steam Deck OLED panels, not Frame displays. | static archive, local browser, fake-Frame |
|
||||
| 110: Dimmer Deck 0.0.4 | 1 / 2 | N/A | Not supported on Frame: Changes gamescope/X11 dimming properties; no mapping to Frame VR display brightness exists. | static archive, local browser, fake-Frame |
|
||||
| 111: Music Control 1.1.7 | legacy / legacy | partial | Panel initializer succeeds with backend/network blocked; actual controls and backend remain unverified. | static archive, local browser, fake-Frame |
|
||||
| 111: Music Control 1.1.7 | legacy / legacy | partial | Exact official backend bound-method calls are repaired in managed installs. An isolated fake dbus-send target accepted player discovery, transport and volume commands after the patch; real MPRIS players, actual Decky ARM64, album art and worn VR remain unverified. | static archive, local browser, fake-Frame, isolated Python backend |
|
||||
| 112: Better Keyboard 0.3.4 | 1 / 2 | broken | This plugin needs root and is not supported by the Frame user service. Frontend audit failed: window.SteamClient.UI.GetUIMode is not a function. | static archive, local browser, fake-Frame |
|
||||
| 113: Decky LSFG-VK 0.12.2 | 1 / 2 | partial | Initializer succeeds; runtime LSFG/Windows DLL downloads and Vulkan integration are not ARM64-validated. | static archive, local browser, fake-Frame |
|
||||
| 114: Network Sentinel 1.0.4 | 1 / 2 | broken | This plugin needs root and is not supported by the Frame user service. | static archive, local browser, fake-Frame |
|
||||
@@ -288,7 +288,7 @@ All published Linux native payloads in this snapshot are x86_64; no published aa
|
||||
| Fantastic | bin/backend | Fan backend/driver is Deck-specific; an ARM64 rebuild would not provide compatible Frame hardware. |
|
||||
| PowerTools | bin/libryzenadj.so, bin/ryzenadj, bin/backend | RyzenAdj and AMD power backend cannot control an ARM64 Frame SoC. |
|
||||
| Memory Deck | bin/libscanmem.so.1.0.0 | libscanmem must be rebuilt for aarch64 and validated against target process architecture; memory writes were not tested. |
|
||||
| Emuchievements | bin/hash | The bin/hash helper needs an ARM64 build and cross-platform hashing tests; no ARM64 artifact is published. |
|
||||
| Emuchievements | bin/hash | A source-matched ARM64 bin/hash helper matched the official x86_64 helper on one synthetic ROM on the real Frame (docs/evidence/framey-emuchievements-arm-2026-10-26.md). The official ZIP still bundles x86_64 bytes and remains blocked; format coverage, packaging, plugin backend/UI, login and worn VR are unverified. |
|
||||
| MoonDeck | python/externals/yarl/_quoting_c.cpython-313-x86_64-linux-gnu.so, python/externals/aiohttp/_http_parser.cpython-313-x86_64-linux-gnu.so, python/externals/aiohttp/_websocket/reader_c.cpython-313-x86_64-linux-gnu.so, python/externals/aiohttp/_websocket/mask.cpython-313-x86_64-linux-gnu.so, python/externals/aiohttp/_http_writer.cpython-313-x86_64-linux-gnu.so, … 26 total; full list in JSON | Rebuild vendored CPython 3.13 extension wheels for the headset Python ABI and ARM64; buddy/helper dependencies also need ports. |
|
||||
| Controller Tools | bin/backend | Native backend needs an ARM64 build and controller/HID dependencies; its direct websocket also bypasses the Framey bridge. |
|
||||
| DeckMTP | bin/umtprd | umtprd needs ARM64 libusb/MTP dependencies and USB gadget permissions; root user-service blocker remains. |
|
||||
|
||||
@@ -80,7 +80,10 @@ def render(data):
|
||||
'Earlier focused checks cover [ProtonDB](evidence/framey-browser-2026-10-25.md), '
|
||||
'[Steam Status/cache](evidence/framey-cache-browser-2026-10-25.md), '
|
||||
'[Storage Cleaner](evidence/framey-api-v1-browser-2026-10-25.md), and '
|
||||
'[legacy MusicControl](evidence/framey-legacy-2026-10-25.md).', '',
|
||||
'[legacy MusicControl](evidence/framey-legacy-2026-10-25.md). '
|
||||
'A [Music Control 1.1.7 backend follow-up](evidence/framey-music-control-bound-methods-2026-10-26.md) '
|
||||
'repairs exact-archive bound-method calls and verifies transport/volume command dispatch against an isolated dbus-send fixture. '
|
||||
'A real MPRIS player and actual ARM64 Decky backend remain unverified.', '',
|
||||
'## Every current plugin', '',
|
||||
'The API column shows manifest / actual bundled frontend API. Many manifests say v1 while their bundle connects API2. '
|
||||
'Missing manifest API is labelled legacy; an unrecognized bundle is unknown. Globals and hardware are static signals, not exhaustive dependencies.', '',
|
||||
|
||||
@@ -196,6 +196,9 @@ u=h/'.config/systemd/user/frame-control-decky.service'; u.parent.mkdir(parents=T
|
||||
self.assertEqual(ssh(f'test -f {installed}/py_modules/click.py && echo yes').strip(), 'yes')
|
||||
receipt = json.loads(ssh(f'cat {installed}/.frame-control-store.json'))
|
||||
self.assertEqual(receipt['compatShims'], ['shotty-click-app-dir'])
|
||||
if plugin['id'] == 111:
|
||||
receipt = json.loads(ssh(f'cat {installed}/.frame-control-store.json'))
|
||||
self.assertEqual(receipt['compatShims'], ['music-control-bound-methods'])
|
||||
job = finished(ok('POST', '/api/decky/plugin',
|
||||
{'action':'remove','id':plugin['id'],'acknowledged':True}))
|
||||
self.assertFalse(job.get('error'), job)
|
||||
|
||||
BIN
Binary file not shown.
+5
-2
@@ -1,14 +1,16 @@
|
||||
# Pinned Decky store test archives
|
||||
|
||||
These are unmodified ZIPs downloaded from the official Decky store CDN on
|
||||
2026-10-25. Each ZIP includes its upstream `LICENSE`. They are test data, not
|
||||
plugins shipped or installed by Frame Control. The fake Frame tests serve them
|
||||
2026-10-25, except `111.zip` downloaded on 2026-10-26. Each ZIP includes its
|
||||
upstream `LICENSE`. They are test data, not plugins shipped or installed by
|
||||
Frame Control. The fake Frame tests serve them
|
||||
from a local fixture so CI does not depend on the store being online.
|
||||
|
||||
| File | Official plugin | Version | Store SHA-256 |
|
||||
| --- | --- | --- | --- |
|
||||
| `7.zip` | CSS Loader | 2.1.3 | `99beaf1374af82408e6d056e0b0147e5fca544e1035fa7ab88419231697bc231` |
|
||||
| `12.zip` | MusicControl | 1.1.6 | `246b89fd653c60a735b1cc401d1b0937d5cb969eea9024561661ff741f081d62` |
|
||||
| `111.zip` | Music Control | 1.1.7 | `3f56a3f56034b277ea74a2c37cc84133b69e382aa6bcb0b43876776aac737ed9` |
|
||||
| `14.zip` | ProtonDB Badges | 1.3.4 | `90a3453ac02709cad64096e7539fd8141fa56f99ae3441fc24a4990dc9684358` |
|
||||
| `123.zip` | Steam Status | 1.2.3 | `9053308e04a74f44745b45b9f4f50af3673348d367e7b14bacc5677de707ac4e` |
|
||||
| `50.zip` | Storage Cleaner | 1.4.0 | `1afa1a6f129c5d419de0e0bf0f6db8d8e50385a5659179346e996031ea372591` |
|
||||
@@ -20,6 +22,7 @@ from a local fixture so CI does not depend on the store being online.
|
||||
The matching source projects are
|
||||
[CSS Loader](https://github.com/suchmememanyskill/SDH-CssLoader),
|
||||
[MusicControl](https://github.com/nikurasu/MusicControl),
|
||||
[Music Control](https://github.com/martinpl/music-control),
|
||||
[ProtonDB Badges](https://github.com/bschelst/protondb-decky),
|
||||
[Steam Status](https://github.com/bschelst/steamstatus-decky),
|
||||
[Storage Cleaner](https://github.com/mcarlucci/decky-storage-cleaner),
|
||||
|
||||
@@ -5,6 +5,8 @@ from pathlib import Path
|
||||
ROOT = Path(__file__).parent / 'fixtures/framey-official'
|
||||
PLUGINS = (
|
||||
{'id': 12, 'name': 'MusicControl', 'version': '1.1.6', 'api_version': 'legacy', 'folder': 'MusicControl', 'sha256': '246b89fd653c60a735b1cc401d1b0937d5cb969eea9024561661ff741f081d62'},
|
||||
{'id': 111, 'name': 'Music Control', 'version': '1.1.7', 'api_version': 'legacy',
|
||||
'folder': 'MusicControl', 'sha256': '3f56a3f56034b277ea74a2c37cc84133b69e382aa6bcb0b43876776aac737ed9'},
|
||||
{'id': 14, 'name': 'ProtonDB Badges', 'version': '1.3.4', 'api_version': 2,
|
||||
'folder': 'protondb-decky', 'sha256': '90a3453ac02709cad64096e7539fd8141fa56f99ae3441fc24a4990dc9684358'},
|
||||
{'id': 123, 'name': 'Steam Status', 'version': '1.2.3', 'api_version': 2,
|
||||
|
||||
@@ -214,6 +214,80 @@ assert copied.read_bytes() == b'local screenshot fixture'
|
||||
self.assertFalse((shotty / 'py_modules/click.py').exists())
|
||||
self.assertNotIn('compatShims', json.loads((shotty / '.frame-control-store.json').read_text()))
|
||||
|
||||
def test_exact_music_control_backend_calls_work_after_managed_install(self):
|
||||
from framey_official import PLUGINS, archive as official_archive
|
||||
plugin = next(p for p in PLUGINS if p['id'] == 111)
|
||||
data = official_archive(plugin)
|
||||
self.assertEqual(hashlib.sha256(data).hexdigest(), plugin['sha256'])
|
||||
folder = self.folder.parent / plugin['folder']
|
||||
args = {'plugin_id': plugin['id'], 'folder': plugin['folder'], 'version': plugin['version']}
|
||||
result = self.apply('install', data, **args)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
receipt = json.loads((folder / '.frame-control-store.json').read_text())
|
||||
self.assertEqual(receipt['sha256'], plugin['sha256'])
|
||||
self.assertEqual(receipt['compatShims'], ['music-control-bound-methods'])
|
||||
with zipfile.ZipFile(io.BytesIO(data)) as bundle:
|
||||
original = bundle.read('MusicControl/main.py').decode()
|
||||
self.assertNotEqual((folder / 'main.py').read_text(), original)
|
||||
|
||||
dbus = Path(self.tmp.name) / 'dbus-send'
|
||||
dbus.write_text('''#!/usr/bin/env python3
|
||||
import os, sys
|
||||
from pathlib import Path
|
||||
args = ' '.join(sys.argv[1:])
|
||||
with Path(os.environ['MPRIS_CALLS']).open('a') as log: log.write(args + '\\n')
|
||||
if 'ListNames' in args:
|
||||
print('method return\\n array [\\n string "org.freedesktop.DBus"\\n string "org.mpris.MediaPlayer2.fixture"\\n ]')
|
||||
elif 'string:PlaybackStatus' in args:
|
||||
print('method return\\n variant string "Paused"')
|
||||
else:
|
||||
print('method return')
|
||||
''')
|
||||
dbus.chmod(0o700)
|
||||
calls = Path(self.tmp.name) / 'mpris-calls'
|
||||
source = '''import asyncio, importlib.util, sys, types
|
||||
from pathlib import Path
|
||||
decky = types.ModuleType('decky_plugin')
|
||||
sys.modules['decky_plugin'] = decky
|
||||
spec = importlib.util.spec_from_file_location('music_control', Path(sys.argv[1])/'main.py')
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
plugin = module.Plugin()
|
||||
async def check():
|
||||
assert await plugin.sp_list_media_players() == 'org.mpris.MediaPlayer2.fixture'
|
||||
await plugin.sp_set_media_player('org.mpris.MediaPlayer2.fixture')
|
||||
assert await plugin.sp_get_media_player() == 'org.mpris.MediaPlayer2.fixture'
|
||||
await plugin.sp_play()
|
||||
await plugin.sp_pause()
|
||||
await plugin.sp_next()
|
||||
await plugin.sp_previous()
|
||||
await plugin.sp_set_volume(0.25)
|
||||
assert await plugin.sp_track_status() == 'Paused'
|
||||
asyncio.run(check())
|
||||
'''
|
||||
called = subprocess.run([sys.executable, '-c', source, str(folder)],
|
||||
capture_output=True, text=True,
|
||||
env={**self.env, 'MPRIS_CALLS': str(calls)}, timeout=10)
|
||||
self.assertEqual(called.returncode, 0, called.stderr)
|
||||
logged = calls.read_text()
|
||||
self.assertIn('org.freedesktop.DBus.ListNames', logged)
|
||||
self.assertIn('org.mpris.MediaPlayer2.Player.PlayPause', logged)
|
||||
self.assertIn('org.mpris.MediaPlayer2.Player.Pause', logged)
|
||||
self.assertIn('string:Volume variant:double:0.25', logged)
|
||||
self.assertIn('org.mpris.MediaPlayer2.fixture', logged)
|
||||
result = self.apply('remove', expected=plugin['sha256'], **args)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertFalse(folder.exists())
|
||||
|
||||
older = next(p for p in PLUGINS if p['id'] == 12)
|
||||
old_data = official_archive(older)
|
||||
old_args = {'plugin_id': older['id'], 'folder': older['folder'], 'version': older['version']}
|
||||
result = self.apply('install', old_data, **old_args)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertNotIn('compatShims', json.loads((folder / '.frame-control-store.json').read_text()))
|
||||
with zipfile.ZipFile(io.BytesIO(old_data)) as bundle:
|
||||
self.assertEqual((folder / 'main.py').read_bytes(), bundle.read('MusicControl/main.py'))
|
||||
|
||||
def test_installed_script_entrypoints_execute_without_zip_exec_bits(self):
|
||||
data, _ = archive(extra=('Example/scripts/start.py',
|
||||
b'#!/bin/sh\nprintf "Framey script: %s\\n" "$1"\n'))
|
||||
|
||||
@@ -228,6 +228,26 @@ try:
|
||||
' base = os.path.join(os.path.expanduser("~"), ".config")\n'
|
||||
' return os.path.join(base, app_name)\n',encoding='utf-8')
|
||||
compat_shims.append('shotty-click-app-dir')
|
||||
# Music Control 1.1.7 passes `self` to its own bound methods.
|
||||
# Python has already bound that argument, so even read-only player
|
||||
# discovery raises TypeError. Repair only the SHA-pinned official
|
||||
# source, and fail the transaction if its expected calls change.
|
||||
if int(plugin_id)==111 and digest=='3f56a3f56034b277ea74a2c37cc84133b69e382aa6bcb0b43876776aac737ed9' and folder=='MusicControl':
|
||||
main=unpacked/'main.py'
|
||||
source=main.read_text(encoding='utf-8')
|
||||
fixes={
|
||||
'self._get_dbus_env(self)':('self._get_dbus_env()',10),
|
||||
'self._sp_player_dbus(self,':('self._sp_player_dbus(',7),
|
||||
'self._sp_dbus_set(self,':('self._sp_dbus_set(',1),
|
||||
'self.sp_get_volume(self)':('self.sp_get_volume()',2),
|
||||
'self.sp_set_volume(self,':('self.sp_set_volume(',2),
|
||||
}
|
||||
for old,(new,count) in fixes.items():
|
||||
if source.count(old)!=count:
|
||||
raise RuntimeError('Music Control compatibility source changed')
|
||||
source=source.replace(old,new)
|
||||
main.write_text(source,encoding='utf-8')
|
||||
compat_shims.append('music-control-bound-methods')
|
||||
# Recheck under the transaction lock. Another store operation may
|
||||
# have installed a colliding name while this archive downloaded.
|
||||
proposed_name=json.loads((unpacked/'plugin.json').read_text()).get('name')
|
||||
|
||||
@@ -1790,7 +1790,7 @@
|
||||
},
|
||||
"backend": "Python + native payload",
|
||||
"hardware": "No specific hardware requirement detected statically; runtime requirements unverified.",
|
||||
"nativePort": "The bin/hash helper needs an ARM64 build and cross-platform hashing tests; no ARM64 artifact is published.",
|
||||
"nativePort": "A source-matched ARM64 bin/hash helper matched the official x86_64 helper on one synthetic ROM on the real Frame (docs/evidence/framey-emuchievements-arm-2026-10-26.md). The official ZIP still bundles x86_64 bytes and remains blocked; format coverage, packaging, plugin backend/UI, login and worn VR are unverified.",
|
||||
"uiSurfaces": [
|
||||
"local/Steam routes",
|
||||
"toasts",
|
||||
@@ -9582,12 +9582,13 @@
|
||||
"https://github.com/martinpl/music-control"
|
||||
],
|
||||
"status": "partial",
|
||||
"reason": "Panel initializer succeeds with backend/network blocked; actual controls and backend remain unverified.",
|
||||
"reason": "Exact official backend bound-method calls are repaired in managed installs. An isolated fake dbus-send target accepted player discovery, transport and volume commands after the patch; real MPRIS players, actual Decky ARM64, album art and worn VR remain unverified.",
|
||||
"blockInstall": false,
|
||||
"verification": [
|
||||
"static archive",
|
||||
"local browser",
|
||||
"fake-Frame"
|
||||
"fake-Frame",
|
||||
"isolated Python backend"
|
||||
],
|
||||
"browser": {
|
||||
"id": 111,
|
||||
@@ -9623,7 +9624,9 @@
|
||||
"sha256": "3f56a3f56034b277ea74a2c37cc84133b69e382aa6bcb0b43876776aac737ed9",
|
||||
"archiveValidation": "accepted",
|
||||
"result": "installed-removed"
|
||||
}
|
||||
},
|
||||
"compatShim": "music-control-bound-methods",
|
||||
"evidence": "docs/evidence/framey-music-control-bound-methods-2026-10-26.md"
|
||||
},
|
||||
{
|
||||
"id": 112,
|
||||
|
||||
Reference in new issue
Block a user