mirror of
https://github.com/baketnk/frame-yap.git
synced 2026-10-06 01:00:04 +02:00
Add user-local desktop launcher and record Frame menu test
This commit is contained in:
1 parent
26113906f0
commit
417f81cb09
6 files changed
+122
-2
No files matched your search
@@ -60,7 +60,10 @@ This is the **local-artifact command shape**, not an available public download.
|
||||
Installation is user-local, retains rollback, refuses active-app upgrades and
|
||||
foreign files, and does not launch or register automatically. Registration uses
|
||||
OpenVR application key `local.frameyap.overlay`, not a Steam store AppID.
|
||||
Autolaunch is opt-in. See [packaging and lifecycle](docs/packaging.md).
|
||||
Autolaunch is opt-in. An installed desktop entry can be selected manually as a
|
||||
non-Steam shortcut. A basic launch from Steam's Non-Steam section opened the
|
||||
panel on one Frame; registration alone did not show an entry in the first checked
|
||||
dashboard menu. See [packaging and lifecycle](docs/packaging.md).
|
||||
|
||||
A pinned GitHub one-command route is implemented in `install.sh --version TAG`,
|
||||
but **do not advertise or run it as a working public installation until a vetted
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# FrameYap TODO
|
||||
|
||||
- [ ] Resolve the Redux inference runtime license before further inference or a
|
||||
bundled release. Obtain written permission covering use and redistribution of
|
||||
M87 Labs Kestrel kernels (including the CPU payload), or scope an independently
|
||||
licensed runtime for the same weights. Inventory the other runtime dependencies'
|
||||
notices/licenses before publishing. The CC-BY-4.0 model weights do not grant
|
||||
permission to use or redistribute the proprietary runtime. See
|
||||
[third-party notes](docs/third-party.md).
|
||||
- [x] Validate a basic menu launch on Frame: OpenVR registration did **not** show
|
||||
FrameYap in the first checked dashboard menu; the user found FrameYap in Steam's
|
||||
**Non-Steam** section and reported that selecting it showed a panel and Quit
|
||||
worked. The server log showed a `--run` overlay connection followed by exit;
|
||||
no FrameYap process remained. The installed native-only package had no bundled
|
||||
runtime or model. This does not validate transcription, recording, text delivery,
|
||||
cold startup, or which shortcut discovery mechanism Steam used.
|
||||
- [ ] Verify the exact missing-runtime panel message and Steam's shortcut
|
||||
persistence across a normal restart (without restarting sessions just for the
|
||||
test). Confirm no runtime/model environment override before future live checks.
|
||||
+41
-1
@@ -89,7 +89,11 @@ input backend; default is `GAMESCOPE_WAYLAND_DISPLAY`, then `gamescope-0`.
|
||||
## Lifecycle
|
||||
|
||||
Install root: `$XDG_DATA_HOME/frameyap` (default `~/.local/share/frameyap`);
|
||||
launcher: `~/.local/bin/frameyap`; existing config is untouched. The launcher
|
||||
launcher: `~/.local/bin/frameyap`; desktop entry:
|
||||
`$XDG_DATA_HOME/applications/frameyap.desktop` (default
|
||||
`~/.local/share/applications/frameyap.desktop`). The desktop entry points to the
|
||||
user-local launcher; the installer never edits Steam's library or registers a
|
||||
Steam shortcut. Existing config is untouched. The launcher
|
||||
passes a stable install-root lock identity and sets `PYTHONDONTWRITEBYTECODE=1`.
|
||||
Runtime/check/registration modes and installer share an exclusive nonblocking
|
||||
`.lock`; no upgrade/rollback/uninstall kills a running app or any other process.
|
||||
@@ -113,6 +117,42 @@ Registration checks actual OpenVR installed state rather than trusting Add's ret
|
||||
alone. Repeated registration/removal were exercised on Frame, autolaunch off.
|
||||
Actual menu launch and cold-runtime behavior remain separate acceptance checks.
|
||||
|
||||
## SteamVR dashboard launcher check (opt-in on Frame)
|
||||
|
||||
Registration creates an **OpenVR app entry**, not a Steam store/library shortcut.
|
||||
On one Frame, registration succeeded with autolaunch off but **no FrameYap entry
|
||||
was visible in the first checked dashboard menu**. The user then found FrameYap
|
||||
under Steam's **Non-Steam** section and reported that selecting it opened the
|
||||
panel and Quit closed it. The VR server logged a `--run` overlay connection and
|
||||
exit; no owned process remained. This establishes a basic menu-driven launch on
|
||||
that device, **not** that OpenVR registration alone creates a Steam shortcut or
|
||||
that the new desktop entry was the sole discovery path. The manifest points to
|
||||
`~/.local/bin/frameyap`, which starts `--run` if launched. A native-only install
|
||||
without a model or authorized runtime should show an **Unavailable** overlay
|
||||
rather than record or run inference. Only perform this check on that native-only
|
||||
installation: verify that `current/runtime/bin/python3` and `current/model` are
|
||||
absent first, and do not press Record, Insert or Enter.
|
||||
The installer also provides a desktop entry; where Steam's UI supports adding a
|
||||
non-Steam app, the user may select that entry or browse to the installed launcher.
|
||||
Shortcut discovery/persistence after a normal restart is not yet verified. Do
|
||||
not hand-edit Steam's shortcut database.
|
||||
|
||||
1. With SteamVR running, register using the command above (do not pass
|
||||
`--autostart`). Leave any other apps and sessions alone.
|
||||
2. In the headset, check Steam's **Non-Steam** section or the **+** application
|
||||
picker for FrameYap. If absent, stop and report the menu checked; registration
|
||||
alone does not add a Steam library shortcut. If present, select it, check the
|
||||
FrameYap panel's status, and use **Quit** to close.
|
||||
3. Report separately whether the menu entry was visible, the click started an
|
||||
app, the panel was visible, and Quit worked. A registration success or CLI
|
||||
`--check-overlay` success alone does **not** establish menu-driven launch.
|
||||
|
||||
This check does not validate microphone capture, transcription, controller input,
|
||||
text delivery or cold SteamVR startup. With a configured inference runtime, the
|
||||
menu launch attempts to load the model; defer that test until runtime licensing is
|
||||
resolved. If an environment/configuration unexpectedly supplies a runtime/model,
|
||||
do not perform this check.
|
||||
|
||||
Before uninstall, explicitly `frameyap --unregister /absolute/manifest/path` and
|
||||
verify success, then run `sh install.sh --uninstall --unregistered`. The latter is
|
||||
an acknowledgement, not a hidden SteamVR edit. Only owned files are removed;
|
||||
|
||||
+20
@@ -230,6 +230,18 @@ def desired_manifest(launcher):
|
||||
"strings": {"en_us": {"name": "FrameYap"}}}]}
|
||||
|
||||
|
||||
def desktop_path(root):
|
||||
return root.parent / "applications/frameyap.desktop"
|
||||
|
||||
|
||||
def desired_desktop(launcher):
|
||||
# Desktop Entry Exec quotes are not shell quotes; escape reserved characters.
|
||||
executable = (str(launcher).replace("\\", "\\\\").replace('"', '\\"')
|
||||
.replace("$", "\\$").replace("`", "\\`").replace("%", "%%"))
|
||||
return ("[Desktop Entry]\nType=Application\nName=FrameYap\n"
|
||||
f'Exec="{executable}"\nTerminal=false\nCategories=Utility;\n').encode()
|
||||
|
||||
|
||||
def check_owned_file(path, expected):
|
||||
if path.is_symlink():
|
||||
fail(f"refusing foreign symlink: {path}")
|
||||
@@ -246,14 +258,18 @@ def check_wrappers(root, launcher):
|
||||
desired = (json.dumps(desired_manifest(launcher), sort_keys=True, indent=2) + "\n").encode()
|
||||
check_owned_file(launcher, desired_launcher(root))
|
||||
check_owned_file(manifest, desired)
|
||||
check_owned_file(desktop_path(root), desired_desktop(launcher))
|
||||
return manifest, desired
|
||||
|
||||
|
||||
def install_files(root, installed, launcher):
|
||||
owned_dir(launcher.parent)
|
||||
manifest, desired = check_wrappers(root, launcher)
|
||||
desktop = desktop_path(root)
|
||||
owned_dir(desktop.parent)
|
||||
atomic_write(launcher, desired_launcher(root), 0o755)
|
||||
atomic_write(manifest, desired)
|
||||
atomic_write(desktop, desired_desktop(launcher))
|
||||
|
||||
|
||||
def receipt(path):
|
||||
@@ -372,6 +388,8 @@ def uninstall(root, launcher):
|
||||
desired = (json.dumps(desired_manifest(launcher), sort_keys=True, indent=2) + "\n").encode()
|
||||
check_owned_file(manifest, desired)
|
||||
check_owned_file(launcher, desired_launcher(root))
|
||||
desktop = desktop_path(root)
|
||||
check_owned_file(desktop, desired_desktop(launcher))
|
||||
versions = root / "versions"
|
||||
if versions.exists():
|
||||
if versions.is_symlink():
|
||||
@@ -398,6 +416,8 @@ def uninstall(root, launcher):
|
||||
launcher.unlink()
|
||||
if manifest.exists():
|
||||
manifest.unlink()
|
||||
if desktop.exists():
|
||||
desktop.unlink()
|
||||
print("FrameYap removed; config and saved models preserved. OpenVR unregister acknowledgement was required.")
|
||||
|
||||
|
||||
|
||||
@@ -219,6 +219,18 @@ def desired_manifest(launcher):
|
||||
"strings": {"en_us": {"name": "FrameYap"}}}]}
|
||||
|
||||
|
||||
def desktop_path(root):
|
||||
return root.parent / "applications/frameyap.desktop"
|
||||
|
||||
|
||||
def desired_desktop(launcher):
|
||||
# Desktop Entry Exec quotes are not shell quotes; escape reserved characters.
|
||||
executable = (str(launcher).replace("\\", "\\\\").replace('"', '\\"')
|
||||
.replace("$", "\\$").replace("`", "\\`").replace("%", "%%"))
|
||||
return ("[Desktop Entry]\nType=Application\nName=FrameYap\n"
|
||||
f'Exec="{executable}"\nTerminal=false\nCategories=Utility;\n').encode()
|
||||
|
||||
|
||||
def check_owned_file(path, expected):
|
||||
if path.is_symlink():
|
||||
fail(f"refusing foreign symlink: {path}")
|
||||
@@ -235,14 +247,18 @@ def check_wrappers(root, launcher):
|
||||
desired = (json.dumps(desired_manifest(launcher), sort_keys=True, indent=2) + "\n").encode()
|
||||
check_owned_file(launcher, desired_launcher(root))
|
||||
check_owned_file(manifest, desired)
|
||||
check_owned_file(desktop_path(root), desired_desktop(launcher))
|
||||
return manifest, desired
|
||||
|
||||
|
||||
def install_files(root, installed, launcher):
|
||||
owned_dir(launcher.parent)
|
||||
manifest, desired = check_wrappers(root, launcher)
|
||||
desktop = desktop_path(root)
|
||||
owned_dir(desktop.parent)
|
||||
atomic_write(launcher, desired_launcher(root), 0o755)
|
||||
atomic_write(manifest, desired)
|
||||
atomic_write(desktop, desired_desktop(launcher))
|
||||
|
||||
|
||||
def receipt(path):
|
||||
@@ -361,6 +377,8 @@ def uninstall(root, launcher):
|
||||
desired = (json.dumps(desired_manifest(launcher), sort_keys=True, indent=2) + "\n").encode()
|
||||
check_owned_file(manifest, desired)
|
||||
check_owned_file(launcher, desired_launcher(root))
|
||||
desktop = desktop_path(root)
|
||||
check_owned_file(desktop, desired_desktop(launcher))
|
||||
versions = root / "versions"
|
||||
if versions.exists():
|
||||
if versions.is_symlink():
|
||||
@@ -387,6 +405,8 @@ def uninstall(root, launcher):
|
||||
launcher.unlink()
|
||||
if manifest.exists():
|
||||
manifest.unlink()
|
||||
if desktop.exists():
|
||||
desktop.unlink()
|
||||
print("FrameYap removed; config and saved models preserved. OpenVR unregister acknowledgement was required.")
|
||||
|
||||
|
||||
|
||||
@@ -77,6 +77,9 @@ class InstallTests(unittest.TestCase):
|
||||
self.assertEqual(manifest["applications"][0]["binary_path_linux"], str(launcher))
|
||||
self.assertEqual(manifest["applications"][0]["binary_path_linux_arm"], str(launcher))
|
||||
self.assertNotIn("binary_path", manifest["applications"][0])
|
||||
desktop = self.data / "applications/frameyap.desktop"
|
||||
self.assertIn(f'Exec="{launcher}"', desktop.read_text())
|
||||
self.assertIn("Terminal=false", desktop.read_text())
|
||||
(root / "config-untouched").write_text("keep")
|
||||
self.install("v1", a1, h1)
|
||||
(self.stage / "bin/frameyap").write_text("next binary")
|
||||
@@ -101,6 +104,7 @@ class InstallTests(unittest.TestCase):
|
||||
self.assertTrue((root / "saved-models/v1/weights.bin").exists())
|
||||
self.assertTrue((root / "saved-models/v2/weights.bin").exists())
|
||||
self.assertFalse(launcher.exists())
|
||||
self.assertFalse(desktop.exists())
|
||||
|
||||
def test_digest_and_same_version_mismatch_leave_previous(self):
|
||||
a, h = self.package("v1")
|
||||
@@ -154,6 +158,20 @@ class InstallTests(unittest.TestCase):
|
||||
self.assertEqual(os.readlink(self.data / "frameyap/current"), "versions/v1")
|
||||
self.assertFalse((self.data / "frameyap/versions/v2").exists())
|
||||
|
||||
def test_foreign_desktop_entry_is_preserved(self):
|
||||
a, h = self.package("v1")
|
||||
desktop = self.data / "applications/frameyap.desktop"
|
||||
desktop.parent.mkdir(parents=True)
|
||||
desktop.write_text("foreign shortcut\n")
|
||||
with self.assertRaisesRegex(ValueError, "foreign file"):
|
||||
self.install("v1", a, h)
|
||||
self.assertEqual(desktop.read_text(), "foreign shortcut\n")
|
||||
self.assertFalse((self.data / "frameyap/current").exists())
|
||||
|
||||
def test_desktop_exec_escapes_field_codes(self):
|
||||
entry = installer.desired_desktop(Path('/home/steam%user/.local/bin/frameyap')).decode()
|
||||
self.assertIn('Exec="/home/steam%%user/.local/bin/frameyap"', entry)
|
||||
|
||||
def test_traversal_and_link_archives_rejected(self):
|
||||
a, h = self.package("v1")
|
||||
self.install("v1", a, h)
|
||||
|
||||
Reference in new issue
Block a user