session/frametop.conf.example: HANDS_POKE_TRAVEL=0.017, the card
pinch's fingers, and the new HANDS_HOLD_LOST_MS, HANDS_FLICK,
HANDS_FLICK_SPEED, HANDS_FLICK_OPENING, HANDS_THROW, HANDS_THROW_SPEED,
HANDS_THROW_DISTANCE and POINTER_GAZE_CONTROL_HOLD. scripts/conf-migrate.sh
moves the example's exact HANDS_POKE_TRAVEL=0.025 line (hands-touch,
never released) to 0.017. hands/README.md's near input section,
docs/reference.md's .resize, settings and handinput state, and the
gesture tables at the top of handinput.h and vr.cpp say what each
gesture does now and what wins. The README notes that the hand glows
are drawn into the cutout buffer, so they don't show with the cutouts
off (the default since the drawn hands).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
session/frametop.conf.example: HANDS_NEAR_REACH=0.075, the new
HANDS_POKE_TRAVEL, HANDS_GRAB_BAND, HANDS_PINCH_CLICK, HANDS_CARD_PINCH and
HANDS_FIST_GRAB. scripts/conf-migrate.sh moves the example's exact 0.30
line (hands-reach, never released) to 0.075. hands/README.md's near input
section, docs/reference.md's settings and handinput state, and the
gesture table at the top of handinput.h and vr.cpp say what each gesture
does near a panel and what wins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Frame's SteamVR driver runs the eye cameras at 15 to 90 fps and, with its
defaults, picks 15. Our tracker then sees 8 or 9 frames in a calibration
dot's 0.6 s, where it needs 15, so it can never calibrate (2026-10-10 report:
"the right eye was seen in only 8 frames" on all 27 dots). With
steamvr.gazeDepEyeToHeadEnable on, SteamVR asks for 90 at every start; the
reporter calibrated once it was on.
gaze/run.sh install turns it on (vrcmd with SteamVR running, else the
settings file), and marks it as Frametop's; gaze/run.sh uninstall and
uninstall.sh turn it off only then. update-check.py (doctor) and
gaze-report.py report the cameras' rate from eyetracking.txt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hold an open hand out to a screen, remote display or floating window,
palm to it, then pull the arm back making a fist: the panel comes over
250 ms to 45 cm ahead of the eyes, at the fist's height, facing you
(user, 2026-10-10). Screens wider than HANDS_GRAB_THAT_WIDTH (0.6 m)
come at that width. The same gesture at a panel that came, still where
it came to, sends it back; the reset button sends floating windows back
(the layout reset places screens and remote displays itself).
screens/grabthat.{h,cpp}: the detector (pure logic): open fingers, palm
ahead and the wrist 28 cm out for 0.2 s arm it; the fingers closing with
the palm still to the panel, then on one frame a fist, the wrist 8 cm
back within 0.5 s and the palm turned over fire it. The panel is the one
the eye-through-palm line meets, else the one the head points at. From
the closing (thumb clear of the index: not a pinch) to 0.4 s after the
fire the hand counts as near, so the helper doesn't take the fist as a
gaze grip. Hands frames carry the head pose at capture now
(Hands::captureHead).
Tests replay the user's flag (fires twice, right hand, at -11.35 and
-5.89 s; expected -11.4 and -5.9), and the other 12 flags and 15 minutes
of recordings fire nothing (1 of 143 pinches, 3 of 12 grips begin on a
claimed hand); synthetic waves, pushes, fists without the pull, slow
pulls, a hand turned over first, held-out pinches, a drop to the lap and
typing don't fire. handinput-test --grabthat FLAG_DIR... scans flags.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Spotlight-like search pill (docs/spotwatch.md). Meta+Space (the input
relay's new spotwatch action, bindable to mouse and controller buttons
too) opens a faux-glass pill straight ahead of the head. Typing finds
apps and remote displays ({host} - {display}). Enter brings a running
app's most recent window to the front with the lazy susan, launches an
app that isn't running as a floating window, or connects and spins to a
remote display. Shift+Enter or a right click opens the item's menu: the
app's desktop actions, Open new window, Open docked on this screen, and
its windows; a display's Go to and Disconnect.
- spotwatch/ft-spotwatch (Python, in the Plasma session's autostart):
the index, ranking (spotsearch.py), keys via libxkbcommon, usage.
- spotwatch/ft-spotpanel.cpp (SteamVR overlay, built by
screens/build.sh): the pill, drawn on the CPU; --render for a look
without a headset.
- ft-screens: "spin to N", "front N", and "spotwatch on|off", which
routes every key to the search while it's open (a lease, renewed
every second) and dismisses it on a click elsewhere or a pause.
- ft-floatd: "spotlist", "activate ID", and launch action=/screen=;
the KWin script tracks when each window was last active.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
voice/ft-voiced (was frame_voice.py) runs in Frametop's container as
frametop-voice.service. It keeps the mic, the wake word and live text,
and hands the keyboard side to the input relay:
- push-to-talk keys are key combinations (dictate, dictate_hold); the
relay sends "toggle|start|stop" to @frametop_voice, taken only from
this user
- text goes to the relay as "type TEXT" (ASCII, in pieces of 2000
bytes), so it lands where typing goes; the relay holds it back while
a modifier is held
- controller buttons are read from vrserver's web socket with
input/vrws.py, in a thread, so the websockets package goes
No own uinput keyboard, fd store, evdev, or controller service any more.
whisper-server runs only while the wake word is on, started and stopped
by ft-voiced. Voice commands and hooks run on the host through
distrobox-host-exec (env passed through env(1)); their FRAME_VOICE_*
variables keep frame-voice's names.
Settings: ~/.config/frametop-voice.json (voice_settings.py, standard
library), wake word off by default, "advanced" for the old config.toml
tunables. voice/run.sh install carries frame-voice's settings over once
(its keys become key combinations) and turns the standalone units off.
Install: install.sh step 8/11 asks (yes by default; --no-voice through
get.sh, install-release.sh and a FrameDrop checkbox). voice/build.sh
builds whisper-server (whisper.cpp d09f61a, Vulkan, static) and
build/venv; voice/models.sh fetches the 322 MB of models into
~/.local/share/frametop/voice by fixed URL and sha256. The image gets
glslc, spirv-headers-devel and pipewire-utils, uv.lock a voice group (sherpa-onnx with its
core pinned: uv dropped it), CI smoke whisper-server and the venv.
uninstall.sh asks about the models; update-check and report.sh know the
service (report.sh leaves out the lines with what was said).
Tests: voice/test (settings, carrying over, typing through a stand-in
relay, push-to-talk, controller buttons from a stand-in web socket) in
just test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For Frametop Voice (voice/, next commits), the input relay does the
keyboard side of dictation:
- "type TEXT" on its control socket types TEXT where typing goes: to
ft-screens for the desktop, else on the virtual keyboard SteamVR
passes to Steam and games. US layout, one key every 4 ms, and a
character waits while a modifier is held. Only from the relay's own
user: the socket now reads each sender's credentials (SO_PASSCRED).
- New actions dictate (a press starts or stops) and dictate_hold
(records while held) send "toggle|start|stop <source>" to
@frametop_voice. They work without pointer mode and while paused.
- A modifier tap bound by its right-hand code ("97") is that key's
alone and comes before the folded one, so dictation's default, a
Right Ctrl tap, doesn't fire on Left Ctrl (crouch in many games).
It's in the defaults once frametop-voice.service is installed.
SHARE_KEYS and @frametop_keys go: they existed only so frame-voice
could see keyboards the relay grabs, and sent every key typed into the
desktop to a socket any local process could bind. conf-migrate.sh drops
the example's line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Display Settings read the log's last line, so an error logged after
"forwarding" (a failed Notify) showed "In the desktop" while forwarding.
It takes the last state line now.
- "on" also forwards to SteamOS's steam_notif_daemon, where notifications are
lost while Plasma's popups are off. The settings app shows it only while
frametop.conf has it, with that warning.
- update-check reads libnotificationmanager.so.1 (its soname) first; the
unversioned .so is a dev symlink.
- justfile: why session/test/test_notifyfwd.py isn't in test-python.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The plan changed from two repos to one: DeeJanuz/frametop transfers to the Frametop
organization, so issues and pull requests go to Frametop/frametop as well (report.sh,
keys-report.py, README, the hand recorder page). The consent text keeps its old link, which
GitHub redirects, so its wording doesn't change. pack/README.md says how the move keeps the old
links and the old one-liner working.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Turn a bare hand palm up toward your face and look at it: after 0.3 s a
small ring shows above the palm and follows it, like the Vision Pro's
home indicator. A thumb-index pinch tap by that hand runs
`ft-steam menu` (opens the Steam menu, or closes the dashboard); the
ring is blue while the pinch is held. ft-screens owns it
(screens/palmmenu.cpp) and reports that hand near while the ring shows,
so the pointer helper doesn't also gaze-click the pinch. The helper is
unchanged. HANDS_PALM_MENU=0 turns it off; `palmmenu on|off|state`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For reports like "windows won't drag while the Steam menu is open" and
"Frametop's keyboard doesn't open", the report now has:
- A section on the keyboard and the Steam menu: the Keyboard setting,
the relay's devices (a pass-through keyboard keeps ours closed by
default), ft-textinput and KWin's input method, the desktop's
QT_IM_MODULE and GTK_IM_MODULE, ft-screens' state and new debug line,
the SteamVR overlays shown, and the matching log lines.
- --watch [SECONDS]: after the report, it records ft-screens' debug line
whenever it changes, and the logs, while the user makes it happen.
- A release's version and container (it reported "container dev:
missing"), the gaze, desktop, eye grabber and Bluetooth units, and the
newest SteamVR start rather than the log's first.
ft-screens: a "debug" command (Steam menu, Steam in front, our keyboard
shown or aside, mode, lasers, held press and which laser, drags in
progress, where typing goes), and log lines when a drag starts, ends, or
can't start, when Steam comes in front or goes, and when a requested
keyboard doesn't open. The relay logs why a focused text field did or
didn't open the keyboard, once per decision (keys-test covers it).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A release runs in the container its .frametop-release names (BOX, as
scripts/in-box reads it), but doctor.sh looked for the dev build box,
so every release install reported "FAIL container dev" (seen on
0.3.0-exp.4).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A SteamOS update deletes every /etc file its keep list
(/usr/lib/rauc/atomic-update-keep.conf) doesn't name. The list keeps
units but not what they run, so after the 0.4.5 update
steamframe-bt-fixups.service and frametop-eyegrab.service failed with
203/EXEC: Bluetooth LE devices stopped reconnecting, and gaze mode fell
back to SteamVR's tracker, which had no calibration.
- Both installers add a drop-in to /etc/atomic-update.conf.d naming
their file; uninstall removes it.
- update-check.py (doctor.sh) fails when a unit's program is gone and
warns when it isn't kept.
- ft-gazed's log and Input Settings' Bluetooth page say what to reinstall.
- setup/README no longer says /etc survives updates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SteamOS 0.4 (0.4.5) is the stable release now, with the eye-server.mmap
layout that the 0.4.3 beta brought (its eye tracker and cv driver differ
from the beta's only in thread priorities and the presence sensor). ft-gaze
and update-check call the layouts "SteamOS 0.3" and "SteamOS 0.4 (+5)"
instead of stable and beta.
update-check also reports when Track Dominant Eye Only is on, warns when
/usr/share/steamos/steamos-cursor.png (the gamescope backend's cursor) is
gone, since SteamOS 0.4's own session moved to /usr/share/holo, and gives
retest hints for steamdeck-kde-presets (its Steam autostart entry changed)
and the new holo-cursors package.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Joins with hands-screens: ft-hands keeps 30 Hz for a middle pinch as well
as for a want-hz reader; GesturePublisher keeps both use() (replay dumps)
and write()'s middle-pinch slots; both test groups run in test-c.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hull shader ran over each piece's box; a forearm across the panel covered a fraction of
it. Now the polygon grown by the margin and the feather is drawn, and fragments outside a
piece are discarded (no blend). The forearm is a stub toward the elbow (18 cm at the average
hand size, as the capsules' 16 cm): the elbow is a guess and a longer piece costs a bigger
redraw. tests/matte_bench.cpp (scripts/bench-matte.sh) times the composite offline on the GPU,
mesh against capsules, frame by frame in turn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- handcut: reads fh_hands version 2 (the skeleton's joints, radii, elbow, pinch point; version 1
still works) and the gestures file; the matte is hand-shaped: a tapered capsule per finger
bone, the palm and the thumb's web as convex slabs, the forearm toward the elbow, drawn with
a soft-edged convex polygon shader; its margin is an angle from the eyes plus motion
(HANDS_MATTE_MARGIN, _MOTION, _MOTION_MAX), the capsules stay selectable (HANDS_MATTE), and a
per-distance correction hook is off by default (HANDS_MATTE_SHIFT). The CPU part is in
handcut.cpp (no GPU or SteamVR calls), the renderer in handrender.cpp.
- handinput: the near claim, poke to click (index straight and nearest, two frames at the
surface, an approach speed or 1 cm deep, release 1.5 cm out or 1 cm back, the click held
still until a 1.5 cm slide, re-press guard, through-the-panel release), pokes on the curve,
reset, dock and close buttons, pinch on the bar, edges, corners, tab or title bar to move
(facing the head) or resize, two hands to stretch and turn, two hands to one, lost hands.
- vr.cpp: hands read once per tick, "hands <L> <R> <allowed>" from the pointer helper, "near
<capture_ns> <L> <R>" to it per hands frame, want-hz while near, glows on the cutout buffer,
hand holders for KWin's button and carried panels, the handinput and cutouts commands.
- tests/handinput_test.cpp: unit tests, and replays of ft-handreplay --publish-dump
(scripts/test-handinput.sh, justfile test-c).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The driver's default role is the treadmill (4), hinted from Activate;
the helper's POINTER_ROLE defaults to treadmill. Needs the driver
reinstalled and a SteamVR restart to take effect.
- The "no hand role" release skips a device whose hint is the treadmill.
- Hand gestures: none begins on a side whose controller is in use, or by a
hand at a controller (by position); a held one ends, as lost, when its
side turns controller.
- The moving-controller release no longer applies while a bare hand's ray
drives the pointer (that ray goes only when its own side turns controller).
- update-check.py: the virtual controller's path, and the touch components.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
get.sh --release reads a release list (frametop.release/v1: releases/stable.json or
experimental.json on Frametop's page, or --manifest), picks the newest release tested on this
SteamOS build, else the newest not known to break on it (asking first), and refuses a build
every release breaks on (--any-steamos overrides). It pulls the release's image by digest,
copies the image's /src/frametop out to ~/.local/share/frametop/releases/VERSION with a
.frametop-release (version, image, commit, channel, container), and runs that copy's
install.sh. The release before stays, for going back; older ones go, with their containers
and images.
In a release tree (FRAME_RELEASE in _env.sh), install.sh builds nothing: it installs the
distrobox the image brings (pack/build/distrobox) and makes the release's own container from
its image (pack/release-box.sh, named after the digest, so installing a release never stops
the one running). scripts/in-box then runs the programs there. install.sh also gets
--bluetooth, to install the Bluetooth fixes without asking.
pack/steamos.json is the SteamOS table: builds tested, and builds that break releases from
"from" up to "fixed_in". pack/release-manifest.py writes release lists with it, for CI. The
pick logic and the lists are tested offline (pack/test/release-test.py, in the gate).
uninstall.sh removes releases, their containers, and their images in its second step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every place an installed Frametop started a program with `distrobox enter dev --` now goes
through scripts/in-box: the pointer and power units, ft-gazed (ft-gaze, ft-eyes), the gaze
check's panel, the desktop's ft-screens, the remote desktop's krdp and VNC, and the three
settings apps. in-box starts the container in a scope of its own (container-up.sh, which now
takes the container's name) and becomes `distrobox enter BOX --`, so callers behave as before.
BOX is "dev" for a source install. A release names its own container in .frametop-release,
so the same tree runs from an image without editing nine call sites. FRAMETOP_BOX overrides
both. Development tools (frame.sh, the headless test, the gaze probe and lab) and hand
tracking, which install.sh doesn't install, keep using the dev container.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
screens/build.sh conflicted: experimental added remote.c to ft-screens, and this branch
links OpenVR through scripts/openvr.sh. Both kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/gaze-report.py checks what gaze mode and its calibration need and
lists findings before the details: the gaze service's unit, checkout and
builds (missing or older than their sources), the frame grabber installed vs
built, SteamVR's eye tracker (process, eye-server.mmap layout via
update-check.py, eyetracking.txt folded), our tracker's shared files,
calibration and ft-eyes status, the service's status and the pointer
helper's gaze mode, a live check that wakes an idle service for up to 20 s
and counts samples, a summary of checks.jsonl runs with why dots weren't
taken, the gaze settings and mappings, and the gaze lines of each log.
Errors in the service's log since its last start become findings too.
scripts/report.sh runs it in place of its own gaze sections.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From a PC, every check ran in the Frame's copy of the repo, which the first sync creates.
Before that, each one failed on its cd or carried on past it: SteamOS and the taskbar
check said ok with a cd error, and distrobox, the container, and free space failed (#44).
They now run in the home folder, and the taskbar check waits for the repo, with a line
saying what copies it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The separate ssh mkdir meant a second SSH login for every sync, and every build and
install step from a PC syncs first. rsync's --rsync-path runs the mkdir before the remote
rsync, in the same connection, and still works with rsync older than 3.2.3 on the PC. The
mkdir now uses the same home-relative path as the rsync destination.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rsync creates only the last component of the destination path, and a
fresh Frame has no ~/dev, so the first sync from a PC always failed:
rsync: [Receiver] mkdir "/home/steamos/dev/frametop" failed: No such file or directory
Create FRAME_REPO over SSH before rsyncing. Fixes#44.
scripts/openvr.sh is the one place for the OpenVR SDK the programs build
against: the v2.15.6 headers, fetched into build/include and checked by
sha256, and the API library. ft-pointer, the ft_pointer driver, and
ft-powerd now build against those headers like ft-screens and ft-gaze,
instead of the 2.1.0 copy in SteamVR's samples, which has no public tag
an image could pin. The library stays SteamVR's own; OPENVR_LIB links
another copy (the image has no SteamVR), and the rpath still names
SteamVR's folder first.
FRAME_IN_BOX=1 tells the scripts they already run in the build container,
so pack/Containerfile can run them instead of keeping a second copy of
every compile line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
screens/tests/relay-buttons-test.c checks relay-buttons.h, built and
run by scripts/test-relay-buttons.sh in the dev container like the
controller click test (-Wall -Wextra -Werror). Which codes go to the
keyboard, the pointer, or nowhere (0x100-0x10f, 0x118-0x15f and
BTN_TRIGGER_HAPPY up dropped, KEY_OK up to BTN_TRIGGER_HAPPY keys); a
press without the pointer on a visible screen dropped along with its
release; a release without a press ignored; a release that still goes
once typing or the pointer moved elsewhere; and ft-screens' own
releases, after which the relay's find nothing held.
It fails against a header whose release needs the pointer, and against
one that makes every code from BTN_MISC up a button, as PR #41 did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The copy is frozen at install time, and SteamOS's entry and steamos-nested-desktop come from
steamdeck-kde-presets, so that package gets a retest hint too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With PR #26 ft-gaze reads the SteamOS 0.4.x beta's layout too, but
update-check.py still knew only stable's: on the beta it reported FAIL (warn
without gaze) and said to "update the offsets in gaze/ft-gaze.cpp", as in
issue #15's report, though the gaze worked.
It now tries both layouts with ft-gaze's own test (EyeFile::Detect: the
timestamp within 2 s of the clock and moving on, both set-1 directions unit
vectors), says which one it found, and fails only when neither fits, for
1.5 s so a blink or a moment with an eye lost doesn't fail it. The failure
says to add the new layout to EyeFile::Detect, and to check again after the
tracker's warm-up when the headset just went on. EYE_NEED is ft-gaze's
kNeed now (it was 0x1d3 against ft-gaze's 0x1f3, now 0x1f8). design.md's
update-check sentence and the offset table in gaze/tracker/findings.md say
there are two layouts.
Checked with made-up files: stable and beta ok, the fields moved by 3
bytes, and a direction always zero, FAIL, an eye lost for two samples ok,
an idle counter skip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frametop.conf.example said HANDS_SWAP_SIDES=0, and desktops.sh copies it on a
fresh install, so every new install forced ft-camd's side camera names and
turned the hand tracker's own side check off. Some SteamVR restarts swap those
names, and then hands land beside their cutouts and recordings are mislabelled.
The example now says auto. scripts/conf-migrate.sh, run by install.sh and
hands/rec/install.sh, replaces the old line only where it's still exactly as
the example wrote it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After 9c66475 and the previous commit, ft-layout's log is
$XDG_RUNTIME_DIR/frametop-layout.log, but report.sh still collected
/tmp/frametop-layout.log, so bug reports lost it, and reference.md still
named the /tmp path.
report.sh reads the new path (it already sets XDG_RUNTIME_DIR to
/run/user/<uid>). reference.md and the comment on RunLayout name the host's
runtime directory, which ft-screens and the desktop start share. The nested
desktop has its own, /run/user/<uid>/frametop, removed at every desktop start
and stop, and the log isn't there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A gaze problem couldn't be told from a report: it had no gaze section. A user's report of
2026-10-05 showed only that the gaze service ran and our tracker was picked.
The report now has a Gaze section: whether the gaze service and our tracker's frame grabber
are enabled and running, when SteamVR's correction and our tracker's calibration were made
(or "none"), and the service's status (ft-gazectl status, on one line: the tracker in use,
whether it's awake and why not, checks.problem). Then the last 10 checks and calibration dots
(checks.jsonl: each dot's reply and whether it was taken), the gaze service's last 60 lines
without podman's exec lines, and the frame grabber's last 20. Nothing in them is an eye image.
Tested on this Frame: each section fills in, and the status is one line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The desktop has its own XDG_CONFIG_HOME (~/.config/frametop), and SteamVR's tools and OpenVR
read their path registry from there. So from a terminal in the desktop:
- pointer/driver/install.sh (install.sh step 4, every reinstall or update from Konsole) wrote
a new registry, ~/.config/frametop/openvr/openvrpaths.vrpath, with only our driver and
"runtime": null, and never registered the driver with SteamVR. That stray file then hid
SteamVR from every OpenVR program started in the desktop.
- scripts/update-check.py (and so doctor.sh and report.sh) reported OpenVR "can't connect to
SteamVR as a background app" (VRInitError_Init_PathRegistryNotFound, or
InstallationNotFound with the stray file) and "vrcmd --overlays lists no overlays". A user's
report of 2026-10-05 showed both, with SteamVR and Frametop's services fine.
- ft-layout's vrcmd calls (the gamescope backend) found no overlays.
They now run with XDG_CONFIG_HOME=~/.config: the driver installer (install, uninstall, probe,
aimhere), update-check.py (for all its checks: nothing there reads the desktop's own config),
report.sh's vrpathreg show, and ft-layout's vrcmd. report.sh doesn't set it for the whole
report, since session/fix-panels.py --check reads the desktop's Plasma config through it.
The driver installer also removes the stray registry (only vrpathreg's, with no runtime), and
update-check.py warns about one. And vrpathreg runs `xdg-open vrmonitor://driverinstalled` to
tell SteamVR's desktop monitor, which the Frame doesn't have: in the desktop that showed "could
not read file vrmonitor://driverinstalled" on every install. A stand-in xdg-open on its PATH
takes it, so install.sh no longer filters the step's output.
Tested in a fake HOME with a copy of SteamVR's registry, a stray one, and the desktop's
XDG_CONFIG_HOME: the new installer removes the stray file, registers the driver in the copy,
and never reaches xdg-open; the old one wrote the stray file, left the copy alone, and ran
xdg-open. update-check.py from that environment: OpenVR and vrcmd ok (the old one fails both);
its stray-registry warning fires on a seeded file. ft-layout's vrcmd: 119 overlays (was 0).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plasma 6.2.5 keeps a panel on a screen number and never moves one whose
number is past the screen count, so a taskbar saved on a spare output
(#18, lastScreen=8 with three screens) or on a screen a smaller layout
dropped stayed hidden. Before Plasma starts, session/fix-panels.py moves
such a panel and its tray's containment to screen 0 (the primary),
keeping its widgets, unless screen 0 already has a panel on that edge.
doctor.sh checks the panels' screens, and report.sh lists them with the
live outputs and panels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The session now turns KWin's blur and contrast effects off by id (blurEnabled and
contrastEnabled in the desktop's kwinrc), and a KWin that renamed them would quietly
leave them on. The check looks for their built-in factories (KWin::blur_factory,
KWin::contrast_factory) in kwin_wayland, which it already reads for --output-count,
and warns if one is gone. The kwin and plasma-workspace retest hints gain the blur and
the hidden autostart entries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vrserver sends a controller's state only when something on it changes, and one lying still
or asleep may not even send its first one. The check subscribed to one controller and failed
after 3 s of silence. It now subscribes to all, and silence after a good handshake is a skip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Frametop kept using the headset during games: with gaze mode off, our eye tracker still took
about 60% of a core, remote desktop about 2 cores while on, and KWin kept drawing hidden
screens because ft-screens sent their frame callbacks at 90 Hz. Pausing gives that back, and
resuming brings back only what pausing stopped. It's also a way to keep the gaze service and
our eye tracker off during games, which PR #13 asked for.
Paused (input/game_pause.py, run by the input relay):
- frametop-gaze stops (ft-eyegrab then idles by itself), and hand tracking and remote desktop
stop if they run
- the desktop hides and slows down: ft-screens "pause on" hides every panel and sends KWin a
frame callback once a second; or, with pause_desktop "close", the desktop closes and starts
again on resume
- the relay lets go of the 3D mouse, typing goes to Steam, and mapped buttons and key
combinations do only pause_toggle, steam_menu and commands
Toggled by both thumbsticks clicked together twice (configurable), read passively from
vrserver's web socket (input/vrws.py) so it works in games and takes nothing from them; by the
new pause_toggle action; by input/ft-pause; and, with pause_auto (default on), by a VR game
starting and ending, which the pointer helper now reports ("vrgame 1|0"). Frametop Input
Settings has a Games page for it. update-check.py checks the web socket, and doesn't count a
paused gaze service as failed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>