Commit Graph
75 Commits
Author SHA1 Message Date
DeeJanuzandClaude Opus 5.5 d0a52f3a7c Model picker: choose hand and eye model versions in Input Settings
Catalogs list each kind's versions with notes, cost and benchmark numbers:
hands/models.json (Latest = v13b-p1, stock) and gaze/tracker/models.json
(classic, SteamVR, eyes-v0 as Testing). Installs read their branch's
catalog from GitHub, so a new version reaches them with a push; offline,
the copy in the install is used.

models/catalog.py (+ ft-models CLI) validates catalogs strictly, downloads
versions from the kind's fixed Hugging Face repo pinned to a commit, and
keeps only files matching their sha256. HANDS_MODEL and EYES_MODEL pick
latest (follows new versions), a version by name, or a builtin. ft-hands
and ft-eyes read them at start; Input Settings restarts hand tracking and
ft-gazed restarts ft-eyes when the setting changes. The services fetch
what the setting needs at start (HANDS_MODELS_UPDATE / EYES_MODELS_UPDATE
= 0 turn that off).

Input Settings: "Hand model" on the Hands page, "Eye tracker" on the Gaze
page (replaces the Own/SteamVR switch), tagged Recommended / Testing /
Older / Built in, with a benchmark table against the model in use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:18:07 -06:00
DeeJanuz 789bb448c6 Merge branch fix/lbraud-pr49 (our fixes for PR #49) into experimental 2026-10-15 16:41:22 -06:00
DeeJanuz 95f990d634 Merge PR #49 from lbraud/fix/kwin-6.6-protocols: ft-screens: offer the protocols KWin 6.6 needs, and present its output layer 2026-10-15 16:41:22 -06:00
DeeJanuz 7eea62620f Merge PR #50 from curiousjtuber/session-logout-watch: Session: end it when Plasma has logged out but KWin stayed 2026-10-15 16:41:22 -06:00
DeeJanuzandClaude Opus 5.5 eaae3dd7d1 ft-screens: present KWin 6.2's buffers on the toplevel again
KWin 6.2.5 (SteamOS 0.4.5) draws each output straight onto the toplevel,
which note_layer_buffer skipped, so every panel stayed empty there. The
toplevel's own DMA-BUF is now noted in screen_commit and competes on size
like a child's; KWin 6.6's 1x1 single-pixel buffer isn't a DMA-BUF, so it
never wins. The surface watcher leaves toplevels to screen_commit, so the
order of the two commit listeners doesn't matter.

A child surface destroyed between its commit and its toplevel's no longer
leaves latest_surface pointing at freed memory (a desynchronized child can).

update-check: libkwin before 6.6 has no host-protocol checks, so that's
ok, not a warning on every current Frame; each libkwin file is read once
(the two symlinks named the same 8.8 MB file).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:39:33 -06:00
DeeJanuzandClaude Opus 5.5 3335cbe26c Report: --voice checks whisper-server, and standalone frame-voice
The mic's one-second sample goes to whisper-server with curl, as ft-voiced
sends it. Only the HTTP status, time and word count are kept. A clip of
silence is flagged, since whisper invents words for it. frame-voice's
units and logs are included when it is installed, since it uses the same
mic and port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:50:18 -06:00
DeeJanuzandClaude Opus 5.5 3e6dba8276 Report: an area per flag, and --watch records each area live
scripts/report.sh takes --system, --desktop, --gaze (--eyes), --hands and --voice; with
none it reports on all of them, as before. Every report keeps the versions and the
settings. New in it: CPU, memory, heat and the busiest processes (--system), and Frametop
Voice (--voice): its services, state, settings (counts, not phrases or commands), speech
models, and the microphone: proxmicmute's gain, PipeWire's capture chain with its errors,
who holds the capture device, and its level over one second (no sound is kept).

--watch SECONDS now runs scripts/report-watch.py for the chosen areas, one thread each:
ft-screens' debug line (desktop), the gaze service's status (gaze), ft-hands' published
frames read under their sequence lock each second (rate, age, each hand's share and
confidence, closest thumb-index and thumb-middle tips) and the side cameras' decision
(hands), Frametop Voice's events with what was said replaced by word counts and the mic's
level each second with its gain when it goes silent (voice), and load, clocks, heat,
memory and the busiest processes every 5 s (system). Then each area's logs from that time.
README's Reporting problems says how.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:16:16 -06:00
DeeJanuzandClaude Opus 5.5 eb31589aed Report: hand tracking's state (models, cameras, sides, ft-hands counts)
scripts/report.sh had no hand tracking at all, so a "tracking is bad" report couldn't
tell a stock model from the pinned one, a failed camera start from bad light, or a
starved tracker. Its new section: HANDS_MODE and the three services, the fine-tuned
models' version and models.sh check, any HANDS_MODELS override, hands/camcheck.py,
sides.json, CPU 5-7 clocks and the hottest thermal zone, ft-hands' starts and last
status, and ft-camd's start lines. --watch adds ft-hands' log (pinches) too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:17:00 -06:00
DeeJanuzandClaude Opus 5.5 4b9f5bf8dd update-check: find XRService by its command line
XRService renames its main thread to "XRServiceLoopTh", so pgrep -x
XRService never matched and the check reported it missing while it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:25:11 -06:00
DeeJanuzandClaude Opus 5.5 afab3cbecc pointer: the 3D mouse is a treadmill again, now that it can move windows
Reverts 797e5c7. With pointer/dashpatch, the treadmill moves SteamVR's
windows by their grab bars, so the default goes back to the role that
leaves both hands to the controllers. docs/design.md explains the cause and
the patch; reference.md lists the patch; update-check.py checks that the
dashboard still has the function the patch wraps and that its debugger is
reachable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:25:11 -06:00
DeeJanuzandClaude Opus 5.5 797e5c7b12 pointer: the 3D mouse is the right hand again, not a treadmill
As a treadmill (the default since 564bb4b), the 3D mouse's laser clicks and
resizes SteamVR's windows but can't move them by their grab bars: the
compositor's window grab follows only /user/hand/left or /user/hand/right,
and logs nothing. With the right hand's role the same laser moves them
(headset test 2026-10-13, SteamVR 2.18.2). The driver's default role is 2
again and POINTER_ROLE defaults to right; treadmill stays a setting.
update-check warns when the device is at /user/treadmill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:33:32 -06:00
DeeJanuzandClaude Opus 5.5 9d6f5984a8 hands: near input on SteamVR's own panels through the pointer helper's laser
The dashboard, the Steam menu, Steam's keyboard and other apps' overlays now
take a poke or a pinch on the content as a click, and a pinch on the border,
like a card or at a corner moves or resizes a floating one. The edge and
corner glows show there too.

OpenVR gives no transform or size for dashboard panels, so ft-screens casts a
7x7 cone of rays from the eyes past each index tip and fits each panel to the
hits' texture coordinates (steampanels.cpp). Near input treats the fitted
panels as foreign: no fist grabs, two-hand stretch, throws, flicks, pushes,
rewinds or two-finger scroll.

ft-screens can't click those panels, so it drives the helper's laser with
"touch" lines (hover, press, move, up, grab, end). A move or resize first
hovers inside the edge and presses the grab bar or resize margin only once a
floating panel's window controls show under it; otherwise the log says why.
The helper keeps its overlay list while hands are allowed and sends it to
ft-screens ("steamvr ...").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:47:25 -06:00
DeeJanuzandClaude Opus 5.5 3fe498ffc3 Merge branch 'hands-r6' into experimental: hand tracking as the main input
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:32:45 -06:00
DeeJanuzandClaude Opus 5.5 a54d89ee86 Docs and settings: corner resize, card fingers, holds, flicks and throws
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>
2026-10-10 19:22:39 -06:00
DeeJanuzandClaude Opus 5.5 9972d53fbc Docs and settings: the 3-inch touch zone, pokes, pinch, card and fist grabs
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>
2026-10-10 18:12:07 -06:00
DeeJanuzandClaude Opus 5.5 a0e46e4954 gaze: keep SteamVR's eye cameras at 90 fps (gaze/eye-rate.py)
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>
2026-10-10 17:58:26 -06:00
DeeJanuzandClaude Opus 5.5 6c921b113c ft-screens: "grab that" brings a panel to arm's reach (HANDS_GRAB_THAT)
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>
2026-10-10 15:49:38 -06:00
CuriousJ 293d8d0329 fix(session): end the session when Plasma has logged out but KWin stayed
With a Deskflow client connected, KWin 6.2.5 can crash on logout: a
SIGSEGV in its libei backend's teardown, since Deskflow sends its input
through libei. KWin's wrapper then starts a fresh KWin with no Plasma,
and that black desktop never ends. The watcher ends the wrapper once
the session manager has been gone for about nine seconds while KWin
stays, which ends startplasma and the session with it.

It ends only a wrapper whose XDG_RUNTIME_DIR is this session's, so
another KWin on the host is left alone. The KWin retest hint in
update-check says when the watcher can go.
2026-10-10 15:02:33 -04:00
DeeJanuzandClaude Opus 5.5 2c472879ed Spotwatch: Meta+Space search for apps and remote displays
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>
2026-10-10 12:08:59 -06:00
DeeJanuz ca1e5c38ab Merge branch 'experimental' into hands-full: a Mac's real monitors through Sunshine, Frametop Voice
# Conflicts:
#	input-settings/ft_input_settings.py
#	input-settings/main.qml
#	input/game_pause.py
#	session/frametop.conf.example
2026-10-10 11:01:32 -06:00
DeeJanuzandClaude Opus 5.5 b3275e7843 Voice: frame-voice becomes Frametop Voice, installed with Frametop
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>
2026-10-10 09:18:14 -06:00
DeeJanuzandClaude Opus 5.5 85f920ff58 Relay: type dictation, take its push-to-talk keys, drop SHARE_KEYS
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>
2026-10-10 08:53:46 -06:00
Lance Braud e68a97a3cb ft-screens: offer the protocols KWin 6.6 needs, and present its output layer
SteamOS build 20261009.6183636 ships KWin 6.6.2. Its nested backend exits
unless the host offers wp_single_pixel_buffer_manager_v1, wp_viewporter,
zwp_pointer_constraints_v1, and wp_presentation, and it draws each output
on a synchronized subsurface. ft-screens offered none of those and presented
the toplevel's 1x1 buffer, so the desktop quit at start.

Lock the layer DMA-BUF when the subsurface commits and present that. Answer
presentation feedback on the toplevel. update-check.py fails when a newer
libkwin names a host protocol this binary does not.
2026-10-10 09:50:05 -05:00
DeeJanuzandClaude Opus 5.5 700f534a27 ft-notifyfwd: settings state from its last state line; keep "on" out of the way
- 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>
2026-10-10 08:33:12 -06:00
DeeJanuzandClaude Opus 5.5 995d407328 Merge utrack/notify-forward: forward the desktop's notifications to the Steam session (#48)
From https://github.com/utrack/frametop/tree/notify-forward. docs/reference.md:
kept both the notifications paragraph and experimental's three-autostart one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 08:32:06 -06:00
DeeJanuzandClaude Opus 5.5 ac5111c552 Merge branch 'experimental' into hands-full
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:44:05 -06:00
DeeJanuzandClaude Opus 5.5 72d9b79d7b Frametop moves to the organization's repo: issues too
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>
2026-10-09 16:17:40 -06:00
DeeJanuzandClaude Opus 5.5 2c9924484d Hands: palm up shows a ring, a pinch on it opens the Steam menu
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>
2026-10-09 11:07:39 -06:00
DeeJanuzandClaude Opus 5.5 4c348e6b2b Merge experimental into hands-full (SteamOS 0.4.5 fixes)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:38:59 -06:00
DeeJanuzandClaude Opus 5.5 1fdd6f0e78 report.sh: Frametop's keyboard, the Steam menu, and --watch to record a problem
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>
2026-10-09 10:03:41 -06:00
DeeJanuzandClaude Opus 5.5 e69fe11b2a doctor: in a release, check the release's own container
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>
2026-10-09 09:27:58 -06:00
DeeJanuzandClaude Opus 5.5 185cb766b1 SteamOS updates: keep the Bluetooth fixes and the eye grabber through them
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>
2026-10-09 08:36:34 -06:00
DeeJanuzandClaude Opus 5.5 06228725cc SteamOS 0.4: name the eye-server layouts by release, check its new parts
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>
2026-10-08 23:33:03 -06:00
DeeJanuzandClaude Opus 5.5 be0314867e Merge branch 'hands-pointer' into hands-full
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>
2026-10-08 22:09:44 -06:00
DeeJanuzandClaude Opus 5.5 cb417e4fb5 screens: hulls drawn as their grown polygons, cut shaders discard outside, a forearm stub; matte bench
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>
2026-10-08 22:01:04 -06:00
DeeJanuzandClaude Opus 5.5 bf6266ce81 screens: hand-shaped matte from fh_hands v2, glows, near hand input (poke, grab, resize, stretch)
- 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>
2026-10-08 21:53:06 -06:00
DeeJanuzandClaude Opus 5.5 564bb4bdc5 Pointer: treadmill role by default, and per-hand last used wins
- 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>
2026-10-08 21:32:14 -06:00
Nikita Koptelov (@ Steam Frame ssh key) 3a63780bbd ft-notifyfwd: forward notifications to an external notify daemon 2026-10-08 23:26:30 +02:00
DeeJanuzandClaude Opus 5.5 eae2f0b754 Releases: get.sh --release installs Frametop built, from an image, by SteamOS build
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>
2026-10-07 21:00:30 -06:00
DeeJanuzandClaude Opus 5.5 6dd9fb1ad8 One launch switch: scripts/in-box runs a program in the container it was built for
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>
2026-10-07 20:54:13 -06:00
DeeJanuzandClaude Opus 5.5 96452bbee5 Merge experimental (remote displays, gaze report, PR #45 and #46) into fix/0x1f6-pr42
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>
2026-10-07 20:51:03 -06:00
DeeJanuzandClaude Opus 5.5 083fa89f52 Report: a gaze report that says what looks wrong first
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>
2026-10-07 08:51:08 -06:00
DeeJanuzandClaude Opus 5.5 eb13cfc5dc Merge branch fix/dkiiv-pr45 (our fixes for PR #45) into experimental
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 08:30:16 -06:00
DeeJanuzandClaude Opus 5.5 c40bb645f2 doctor: checks that don't need the repo work before the first sync
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>
2026-10-07 08:30:09 -06:00
DeeJanuzandClaude Opus 5.5 195fb1e441 sync: make the Frame's repo dir in rsync's own connection
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>
2026-10-07 08:29:21 -06:00
DeeJanuzandClaude Opus 5.5 2052dff70d Merge PR #45 from dkiiv/fix/sync-mkdir-parent: sync: create the Frame's repo dir before the first rsync
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 08:21:02 -06:00
dkiiv a94af27131 sync: create the Frame's repo dir before the first rsync
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.
2026-10-07 01:02:39 +00:00
DeeJanuzandClaude Opus 5.5 54ec0853aa Build scripts: one pinned OpenVR recipe, and they run inside the image too
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>
2026-10-06 09:54:08 -06:00
DeeJanuz 4ba49af487 Merge branch fix/supertuxii-pr41 (our fixes for PR #41) into experimental 2026-10-06 08:54:35 -06:00
DeeJanuzandClaude Opus 5.5 ac692c38ea ft-screens: an offline test of the relay's mouse buttons
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>
2026-10-06 08:48:49 -06:00