mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 08:00:09 +02:00
917ef9d29c339b82cab45d6c427c914e02db7d7e
37
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9d7c8a0b91 |
Experimental (#29)
* Input relay: typing with the pointer helper down no longer ends the relay Typing on a pass-through keyboard tells the helper "typing". With the helper not running (SteamVR off), that send raised ConnectionRefusedError and the relay exited, dropping every grab until systemd restarted it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ft-steam: open Steam's menu through Steam's own UI steam/ft-steam menu opens the SteamVR dashboard on Steam's menu, or closes the dashboard if it's up, without pointer mode: it asks Steam's UI over its debugging port to show its dashboard overlay (ShowVROverlay, what Steam calls itself) and focus the Steam frame's left menu (MenuStore.OpenMainMenu). ft-steam check says whether those calls still exist, and update-check.py runs it, since a Steam client update can rename them. The CDP client moves from display-settings/steam_settings.py to steam/steamui.py, so both use it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Shortcuts: Steam menu, commands, and modifier taps Key combinations (and mouse and controller buttons) get two new actions: - steam_menu: Open Steam menu / close dashboard (steam/ft-steam menu). - command:CMD: run CMD with sh -c, as the relay's service, with layout/, float/ and steam/ on its PATH. Input Settings offers it for key combinations as Run a command... Both work without pointer mode. A modifier on its own is now a key combination too: a tap, pressed and released with no other key, mouse button, or scroll in between. A bound tap sends the desktop F24 before the release, so Plasma's launcher stays shut. The defaults gain a Meta tap for the Steam menu; this replaces META_DASHBOARD, which only worked in pointer mode. input/test/keys-test.py runs the relay against fake devices with every outgoing socket renamed, so it's safe next to the live relay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Relay: share a key combination's Meta release with frame-voice A Meta+key combination (Meta+J for gaze_left, say) hides Meta's release from the desktop, and the relay skipped share_key for it too. frame-voice saw Meta go down on @frametop_keys and never come up, so it held all dictated text back, waiting for that release. Keys of grabbed keyboards are now shared as pressed, before key_binding() decides what the desktop gets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hands: ft-cutouts, the hand cutouts without pinches and grips hands/ft-cutouts on|off|status starts ft-camd and ft-hands as transient user units with ft-hands' new --no-gestures: hands are published for ft-screens' cutouts, but no pinch or grip is detected, so nothing clicks or drags and a closing hand doesn't raise the tracking rate. It needs a build and ft-camd's capabilities, not hands/run.sh install. Its units conflict with ft-handsctl's, so each stops the other, and they stop with SteamVR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ft-cutouts status: only the current run's tracker lines Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Gaze: say why gaze mode can't work yet, and open the calibration whenever it's missing Turning gaze mode on without a calibration opened Calibrate only on the off-to-on change, and only if it could open right then. With the headset off, the eye tracker silent, or the panel not built, or with gaze mode already on when the gaze service started, nothing opened and nothing said why: the pointer just stayed a mouse. - The gaze service now checks every second: gaze mode on, no calibration for the tracker in use, eyes seen -> the full calibration opens. One that closes unfinished opens again only after the headset comes off and on, gaze mode off and on, or Calibrate, so it doesn't loop. A start that fails retries every 10 s. - Its status says why gaze mode can't work yet (checks.problem): not calibrated and opening, open, closed unfinished, or can't open and why. - Input Settings shows that under the Gaze pointer switch, along with the gaze service not installed or not running and our tracker missing its frame grabber. - ft-gazectl on notes a missing calibration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Gaze: install our eye tracker, prefer it, and say why a calibration dot wasn't taken A user on a fresh install got "Calibration failed: only 0 of 21 dots" with no reason. The installer never installed our tracker, so gaze used SteamVR's, and the only way SteamVR's tracker rejects a dot is losing an eye for most of the look. The panel just showed a red ring. - install.sh: step 9/10 installs our tracker (gaze/tracker/install.sh) after gaze mode, yes by default; it needs sudo, so --yes runs it only when sudo won't prompt. If it fails, gaze keeps SteamVR's tracker. Configs that still say GAZE_TRACKER=steam (the old template) are asked whether to switch. - GAZE_TRACKER=auto, the new default: ours when it's installed (the frame grabber, its unit, and ft-eyes' Python), else SteamVR's. ft-gazed rechecks every second, so installing it switches over. Input Settings lists Own tracker first as recommended, and says how to install it when it's missing (checking the host's /etc through /run/host from the dev container). - The calibration panel has a note line, orange over the instructions: why a dot wasn't taken (an eye lost, a blink, the eyes disagreeing for SteamVR's tracker, from steady_samples' new drop counts; ft-eyes' reply for ours), what a click is still waiting for after 1.5 s, and a failed calibration's most common reason, which the Gaze page shows too. steady_samples keeps the same samples as before (checked on 2037 windows of recordings); a lost eye is named before a blink, since its openness reads 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * README: link the Frametop Discord Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Wait for a new container to finish setting up before entering it container-up.sh starts the dev container in a scope of its own, so distrobox enter finds it running and skips its wait for distrobox-init. On a fresh install, init was still setting up passwordless sudo when dev-container.sh ran sudo dnf install, and sudo asked for a password with no terminal to read it from. container-up.sh now waits for container_setup_done itself, and the container's sudo calls use -n, so a password prompt fails at once with a clear message. Fixes #9 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Prevent small desktop overlay pointer movements from starting a drag * Clear reported drag state on controller release * Click stability: only a hand controller's press starts it The 3D mouse drives SteamVR's laser through the ft_pointer virtual controller, so its events reach the screens the same way a controller's do. The filter held every press, which turned the mouse's short drags (selecting a character or two, nudging a slider) into clicks. Mark button events from hand controllers and start the filter only on those. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Input relay: retry a new device until udev gives it to the input group A new /dev/input node is root:root 0600 until udev applies GROUP=input. The scan probed each new node once and marked it seen even when the open failed, so a node caught in that gap was never opened. Behind a KVM, a switch brings back a hub of devices at once: on the Frame, four nodes failed with EACCES in one switch, the keyboard was never grabbed, and its keys went to gamescope instead of the desktop screens. A node that isn't readable yet now waits for the next scan. * Screens: take a screen's overlays from one copy in the catcher While a button pressed on a screen is held, UpdateCatcher checks every tick whether the laser is still on one of the screen's overlays. It built that list from s.All().begin() and s.All().end(), but All() returns a std::array by value: iterators into two different temporaries, which is undefined behaviour. A clang build of ft-screens got a garbage length, threw std::length_error, and aborted on the first click, taking KWin and the desktop with it. * Gaze: leave SteamVR's gaze action alone during VR games From curiousjtuber's PR #13: with the gaze service running, SteamVR restarted its eye tracker every 10 to 13 s of Beat Saber, as if the headset came off, and each restart took input focus from the game. The PR stopped every read in a game. Only the action path reaches SteamVR (UpdateActionState on the gaze set at overlay-global priority, then GetEyeTrackingDataRelativeToNow); the mmap and our tracker are read-only files. So only the action is skipped while a scene app runs, and gaze keeps moving the pointer over the dashboard in a game. The action source is only used with --source action. Co-Authored-By: CuriousJ <curious.j.tuber@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hands: --record-hz, and the hand recorder's design (hands/rec/DESIGN.md) ft-hands --record-hz N records at most N frame sets a second, for the hand recorder (10). DESIGN.md lays out the recorder: the headset panel, the session runner and its script, the files, review and export, consent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hands: ft-handpanel, the hand recorder's headset panel A head-locked SteamVR overlay for the hand recorder (hands/rec/DESIGN.md): 1.2 m ahead, 12 degrees up, 36 degrees wide, drawn with stb_truetype into three shared DMA-BUFs as ft-gazepanel does. It shows the title, step, wrapped instruction, note, countdown, hand chips, near/far bar and a "Paused" cover, driven over @ft_handpanel. It also places the touch target, a 2 cm dot in its own overlay fixed in the room where the head was at the first command for that point, and logs head and controller poses to poses.jsonl at 250 Hz from a thread of its own. Both threads take one lock around OpenVR calls. --no-vr prints each picture's state to stdout (and --dump writes the pictures), for testing without a headset. hands/rec/build.sh builds it in the dev container. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hands: the recorder's worn check goes by the panel's backlight, as frame-job does Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hands: the hand recorder's session runner and guided script hands/rec/session.py runs a recording session from script.json: it starts ft-camd and a tracking ft-hands as transient units only if they aren't running, records each section as one take (ft-hands --record-only at 10 sets/s, a new sets-N.bin after each pause), drives ft-handpanel, and writes session.json, calibration.json (identifying fields removed), prompts.jsonl and take.json. Feedback comes from the live hands file and, in the controller sections, from the panel's device poll. It also runs from the command line (--dry-run, --speed, --ring, --no-start). hands/rec/script.json: 11 sections, about 9 minutes without the object and controller sections. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hands: the hand recorder's window, review and export hands/rec/ft_handrec.py + main.qml (Kirigami, dev container; host launcher hands/rec/ft-handrec): consent (CONSENT.md, asked again when its version changes; profile.json with a random contributor id), the before-you-start checklist with the lighting and free-space checks, the session controls (Space pauses, Esc stops), review with a frame-set viewer that deletes ranges, takes and sessions, export with progress and cancel (warns while the headset is worn), and the upload page (UPLOAD.md, the huggingface-cli command; HF_DATASET is a placeholder). --dry-run runs sessions without processes, for testing. hands/rec/takes.py (standard library): indexes sets.bin and sets-N.bin without reading pixels, reads one set's cameras, keeps deleted ranges in take.json, and exports: deleted sets left out, zstd -10 -T2 at nice 19, manifest.json and SHA256SUMS, nothing left behind on cancel. CONSENT.md and UPLOAD.md are drafts pending a legal review; the window says contributions aren't open yet. The dev container gains zstd. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hands: upload from the hand recorder's window, export checks, a rehearsal hands/rec/validate.py (standard library; Linux and Windows, Python 3.12+) checks an export before upload and when it's received: SHA256SUMS, an allow-list of files, the manifest's schema and keys, the consent version, a uuid4 contributor, no identifying fields in calibration.json or device.json, every sets.bin.zst decompressed to its end as a stream with each FHSET01 header checked against the manifest, jsonl lines, the total size. It decompresses with compression.zstd, zstandard or the zstd program. validate.py DIR [--json]. hands/rec/hub.py uploads an export with huggingface_hub, as a pull request to contributions/<contributor>/<session>: validate first, refuse a repeat of the same export, check the login (whoami) and access (auth_check), upload_folder(create_pr=True) with the manifest summary as the description, then record the PR under "uploads" in session.json. Errors are explained (terms not accepted, not found, 401/403, network). --dry-run makes no network calls. FT_HANDREC_DATASET overrides HF_DATASET (DeeJanuz/frametop-hands); while the texts are drafts a real upload needs FT_HANDREC_ALLOW_UPLOAD=1. The Upload page shows the login with "Check again" and how to run hf auth login in a terminal (the token never enters the window), then Upload with a phase, progress and Cancel (hub.py as a child process), the PR link, and a warning for an export uploaded before. The manual command stays as the fallback. ft-handrec --hub-dry-run. session.py also saves device.json: cv.cad_from_cal and head from /persist/device_config.json, the labeller's shape, nothing identifying; export copies it. Session ids with a -N suffix are accepted everywhere. hands/rec/rehearse.sh runs it all without the headset: ft-ringplay plays 30 s of a capture into a ring, session.py records a short test script with ft-handpanel --no-vr and a tracker, then export, validate and a dry-run upload (--repo ID uploads for real). It runs in one frame-job scope, deletes its data and stops its processes, also on Ctrl+C. hands/rec/tests/test_validate.py covers good and broken exports and hub.py without the network. The dev container gains python3-huggingface-hub. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * README: Frametop doesn't work on the SteamOS beta yet Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit |
||
|
|
12c42cd455 |
Bring the docs up to date with the code
- floating-windows.md and profiles.md describe what's built, with what isn't listed as such; the plans, phases, and branch notes are gone - hands-migration.md is gone: the move is done; its open items are in hands/README.md's Known issues - reference.md: Layout & profiles, every action, key combinations, floating windows, the gaze pointer, and hand tracking as they are - design.md gets the KWin findings from floating-windows.md - README, gaze/README, AGENTS, hazards, gaze-controllers, and the example config catch up with gaze, hands, and the stuck-key fix Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
20e8d6254a |
Merge gaze-calibration into experimental
The gaze checks and calibration in a headset panel (quick, five, full, headset fit; shared-buffer drawing, click-to-capture), keyboard and mouse gaze clicks (Meta+J/K, the mouse's buttons alike, mouse moves only while a button is held, double right/Meta+K tilts a drag), one 55 degree learning limit with a quick check past it, eye presence for "the headset went on", the gaze probe as a development tool, and the relay releasing keys the desktop has down that no keyboard holds. Conflicts with the profiles: the relay keeps known_action/needs_pointer and the gaze defaults (Meta+J/K and the float key); Input Settings keeps the profile actions everywhere and the gaze actions in key combinations only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
45b1b06cdb |
Calibration all in the headset panel; the gaze probe is a development tool
Check headset fit now runs in the headset panel too ("fitcheck"): a card per
eye (tracked or lost, the tracker's signal, how much of the last 10 s it was
seen) and the hints, from the probe's fitcheck.py, updated at most twice a
second; left click or Meta+J runs its guided check, right click or Meta+K closes
it. So Quick check, Calibrate, and Check headset fit all happen in one place.
The gaze probe moves to the Gaze page's overflow menu as "Gaze probe
(development)", and its app menu entry says it's a development tool. Texts that
sent users to it ("use Calibrate… with Own tracker") point at Calibrate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
964c250858 |
Gaze mode: a double right click or double Meta+K pans and tilts a drag
A drag begun by right during the left's held-back press (or Meta+K during Meta+J's) now lasts while either button or key is held, so pressing the right one again is free: it tilts, as a right press does during any mouse drag. Meta+K during a keyboard drag (held still into one, or the second Meta+K) tilts while held: the head turns the panel, and the mouse can too; let go and the head drags again from there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1f46616c75 |
Gaze mode: the mouse's buttons work like Meta+J and Meta+K
The right button's press is held back like the left's: hold it, the pointer stops where you look, move onto the target, and the right click comes on the release (held still, it's a real right press). Right during the left's held-back press now starts a drag where the pointer is, as Meta+J then Meta+K does, in place of a right click there; letting go of either drops it. So once you've moved, the left alone only clicks, and the right starts a drag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a2a5a4c98e |
Gaze mode: the mouse only corrects, and left-then-right right-clicks
POINTER_GAZE_MOUSE_MOVE=held, the default (the Gaze page's Mouse movement switch, with why it's on): while the gaze has the pointer, moving the mouse does nothing; it moves the pointer only while a button is held, as a correction. A bumped or drifting mouse can't pull the pointer off what you're looking at, and every mouse move is a correction, so lessons aren't polluted by mouse moves to somewhere else. With the gaze stale for a second, in a game, or with the headset off, the mouse moves the pointer as usual. "free" is the old behaviour. Pressing the right button while the left one's press is held back right-clicks where the pointer is instead (correct with the left, then right-click); both releases are then nothing. Meta+J then Meta+K stays a drag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8998fc1ddd |
Gaze learning limit: 55 degrees, past it a quick check; a solid panel
Drops the quick check's second step (
|
||
|
|
362968198b |
Quick check: put the pointer on the dot, and one limit for learning
After the quick check's capture, in gaze mode, the dot stays where it is in the
room (the panel's new "lock") and the gaze has the pointer again. You put the
pointer on the dot as you'd correct a click, with the mouse or Meta+J and the
head. That click clicks nothing: the helper sends it as "calverify", learned
whatever its size. Over POINTER_GAZE_NUDGE_MAX, the capture runs again, up to
3 times. A right click or Meta+K skips it.
POINTER_GAZE_NUDGE_MAX is now the one limit: 15 degrees by default (was 8),
and ft-gazed's own limits (8 for SteamVR's tracker, 25 for ours) follow it. A
keyboard correction past it isn't learned but opens a quick check ("recheck"),
in place of the 30 degree keyboard limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
4c46d69b0c |
Check what Frametop needs from SteamOS after an update
On the Frame, a SteamOS update replaces SteamVR, KWin, and gamescope with the rest of the OS image. scripts/update-check.py, run by doctor.sh and report.sh, checks what Frametop uses from it: the OpenVR interface versions the installed programs were built against, the vrcmd --overlays format, the eye tracker's shared memory layout, host files, services, sockets, and the driver registration. doctor.sh --mark-good records the package versions once things work, and later runs say what changed and what to try by hand. The session no longer stops when mesavars.sh or flatpak.sh is missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c4c10e6d92 |
Gaze checks and calibration in a panel fixed to the headset
ft-gazepanel (gaze/panel) is a SteamVR overlay that stays put in your view and shows dots at known head-relative directions; the gaze service runs it and drives it (gaze/gazecheck.py): - a one-dot quick check 3 s after the headset goes on, when our tracker asks for a click (reseat), at most every 2 minutes, and from Quick check on the Gaze page; five dots follow if the next 3 lessons are still over 2 degrees off - the full calibration (the probe's three rounds, dark to bright) when gaze mode comes on without one, or from Calibrate; quitting it with still no calibration turns gaze mode off Each dot takes the gaze once it has held still for 0.6 s; a left click or Meta+J takes it at once, a right click or Meta+K closes the panel (the pointer helper hides its dot and passes those presses on while "calpanel" lasts). Our tracker gets clicks and its own calibration; SteamVR's gets lessons per eye, or a new calibration.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e16b7588e2 |
Gaze mode is a mouse feature: drop its controller parts
Steam reads the Frame controllers itself, outside SteamVR's bindings, and every press and release it sees takes SteamVR out of laser mode, so controller clicks at the gaze can't be done cleanly (docs/gaze-controllers.md, from the gaze-first branch's tests). - Gaze precision and Gaze drag take a mouse button or a key combination only; the controller source, its aim steering, and POINTER_PRECISION_GAIN, POINTER_PRECISION_DEADZONE and POINTER_GAZE_DRAG_GAIN are gone. - The gaze actions (including Gaze pointer on/off) can't be mapped to controller buttons: the relay ignores them there and doesn't ask the helper for those buttons, and Input Settings no longer offers them. - Last used wins in gaze mode too: picking up a controller hands it the laser, as docs/design.md already said. - The gaze dot shows all the time (POINTER_GAZE_DOT=always, the default; moving brings back the old behaviour), with a switch on the Gaze page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5714978e65 |
Add a keyboard for text fields on the desktop
Fixes #7: SteamVR's keyboard never came up for the desktop's apps, and opening it for our panels doesn't work well on the Frame (it's Steam's own panel, mounted in the dashboard's scene, it follows the laser between panels, and it takes the controllers over to SteamVR's laser). - KWin starts input/ft-textinput as the desktop's input method. It tells the input relay when a text field gains or loses focus, and the relay asks ft-screens to open or close the keyboard. The session drops the QT_IM_MODULE=xim and GTK_IM_MODULE=xim that the gamescope session sets, or Qt and GTK apps never report text fields. - The keyboard is ft-screens' own panel (screens/keyboard.cpp): a US laptop layout, typed with a controller's laser or the 3D mouse. It opens 0.7 m in front of you, below your eyes and facing you. It has a grab bar to move it, a Close key, latching Shift, Ctrl and Alt, and repeat on a held key. It's drawn into shared DMA-BUFs, so it doesn't flicker. Its keys reach the focused screen as key presses, so every app takes them. - It steps aside while the Steam menu or Steam's own keyboard is up and comes back after. A layout reset closes it, and it doesn't open without a head pose. - Frametop Input Settings has a Keyboard page: open it for every text field, only while no keyboard is connected (the default), only from a mapped button (the new Open/close keyboard action, for mice and controllers), or never. A switch keeps it open until you close it. - The pointer helper treats every frametop.* overlay as a real panel. The keyboard's shared texture reports 0x0 like SteamVR's scene-graph controls, and the helper had given it their wide catch radius. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3aea571496 |
Start Frametop's SteamVR clients only once SteamVR is up (#6)
The pointer and gaze services need steamvr.service to be running (Requisite=), and ft-pointer, ft-screens, and ft-gaze connect as a background app before switching to overlay, so they never start a vrserver of their own. One started from the dev container never finds the headset, which left a reboot stuck in a loop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit
|
||
|
|
d888b6c3f1 | Merge branch 'pointer-ignore' into experimental | ||
|
|
9c04207bb9 |
Let the pointer pass through panels you pick, like a performance overlay
A head-locked performance overlay kept catching the 3D mouse. It has no
input method, so SteamVR's laser passes through it, but the helper hit
tests every visible overlay with ComputeOverlayIntersection, and the dot
stuck to it whenever it crossed that corner of the view.
POINTER_IGNORE in frametop.conf now lists overlay keys the helper leaves
out of the collision, comma-separated shell patterns, so "vendor.app*"
covers a whole app, including panels it opens later. The laser starts
just before the cursor point, so an ignored panel nearer to you doesn't
catch it either.
Frametop Input Settings has a new Ignored panels page. It asks the
helper for SteamVR's overlays ("overlays", answered from the list's
thread once vrcmd has run again, even while the pointer is off), groups
them by app, and has a checkbox per panel and one for the whole app.
Frametop's own screens aren't offered, since ignoring one would leave
nothing to click the app on with the mouse. Entries for apps that
aren't open are listed so they can be removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
ed9542d3b8 |
Merge branch 'layouts-headpin' into experimental
# Conflicts: # README.md # display-settings/ft_display_settings.py # display-settings/main.qml # docs/reference.md |
||
|
|
b99fb97f32 |
Turn the displays off while the headset isn't used, and keep it awake on a charger
A display mount that covers the proximity sensor makes the headset seem worn, so SteamVR never turned its displays off and they stayed on all night. The new power service, ft-powerd (frametop-power.service), goes by use instead: after DISPLAY_OFF_MIN minutes in which the headset and controllers didn't move and no input device was used, it turns the backlight off, and the next movement or input turns it back on. The new Power tab in Frametop Display Settings sets that time and has a Stay awake while plugged in switch. The switch sets Steam's own "When Plugged In and Idle -> Sleep after" to Never through Steam's UI, so the Frame stays reachable remotely while the power button still works. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0be802c7e1 |
Add named layouts and a head pin for screens
Named layouts: Save current arrangement in Frametop Display Settings now asks for a name, and saved layouts are listed with the presets under Arrangement, with rename and delete next to the list. A named layout is the custom arrangement under a name: each screen's place relative to your head, width, curve, and pin, but not resolution or scale. Using one copies it into the custom arrangement, so desktop start, Meta+Shift+R, and Arrange now apply it unchanged; "active" remembers the name, and a plain capture clears it. ft-layout gains save, use, layouts, rename, and delete. A layout saved with fewer screens than there are now leaves the others where they were saved last, or where the preset puts them. Head pin: ft-screens' pin command takes "head" as well as left and right, and pins the screen to the headset (device 0) where it is, like a HUD. A head-pinned screen skips the wrist facing rule and shows whenever the screens do. Carrying it re-pins it to the head on release, like a wrist pin, so it can be adjusted in VR. The Visibility tab (now Visibility & pins) sets each screen's pin: in the room, either wrist, or your head, and the pin command now rejects anything but left, right, or head (it used to take anything else as left). This removes the "no HUD" limit from the docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f01c84f9a3 |
Use the other eye when the tracker loses one, and add a headset fit check
SteamVR's combined gaze keeps going on one eye, but it holds the lost eye's yaw, so the gaze moves half as far sideways as the eyes do. ft-gaze now reads each eye's tracking uncertainty and raw measurement from eye-server.mmap. When the tracker loses an eye, ft-gazed takes the gaze from the other one, plus the offset that eye usually shows against both, learned while both are seen. On a recording, one eye alone came out a median 0.8 degrees from both eyes' gaze. Glances down at the keyboard, past every screen, aren't sent. The pointer stays put, and eyes lost there don't count as lost. The gaze probe gets a Headset fit mode. It shows per-eye tracking, openness and confidence, maps where each eye gets lost, gives hints, and has a guided check. The settings app opens it from the Gaze page and shows how often each eye is lost. The probe can also test each eye alone, and its side panel now collapses to a title bar so the dot isn't hidden behind it. Snapping to UI elements is deferred; the mouse drag is the correction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0857a54fab |
Don't let resting controllers take the laser from the mouse
A controller released the 3D mouse's pointer on a single pose sample faster than 0.35 m/s or 2 rad/s, once the mouse had been still for 500 ms. The helper polls about every 8 ms, so one noisy sample was enough: a knock on the desk, or a tracking jump when the headset's cameras pick a resting controller up again (#1). A controller now has to stay over the limit for 100 ms in a row, and only samples with a normal tracking result (Running_OK) count. The new POINTER_CONTROLLER_PICKUP setting (1 by default, 0.5 to 5) scales both limits; it's a slider on the Pointer page of Frametop Input Settings and applies live. A controller picked up for real still gets the laser back through SteamVR's hand role, which follows its touch sensors. The release log now records the speed and spin that triggered it, to tune the default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
14879023f3 |
Let the 3D mouse click all of SteamVR's Settings page
Most of SteamVR's Settings page took no clicks from the 3D mouse: they went through to a desktop screen behind it, and the page covered the dot. Steam's pages (Library and the rest) are drawn in valve.steam.gamepadui.main, which ComputeOverlayIntersection hits exactly. For SteamVR Settings that overlay is hidden and the page is drawn by the dashboard's scene-graph panel, whose shape OpenVR doesn't give out. The helper guessed a plane and a 0.5 m circle from the panel's transform, but its origin is at the page's left edge and the page's surface is nearer than that plane, so the laser, starting a few cm in front of the guess, started behind the page. On that page only (dashboard open, the main overlay hidden, the line of sight crossing the page's measured area), the laser now starts 25 cm from the eye so SteamVR's own hit test finds the page. The dot is drawn 0.6 m out, in front of the page, and the laser-catching dot sits 8 m out, invisible, with SteamVR's hit dot hidden on it. The beam and SteamVR's hit dot show there, like a controller's; everywhere else nothing changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0d50efec45 |
Map the Frame controllers' buttons; make gaze clicks correctable
The Frame controllers aren't input devices on the host, so the pointer helper reads them with SteamVR input (vrbuttons.h, actions/), one action set per button, and sends presses to the relay, which does the mapped action like for a mouse button. Only mapped buttons are taken, at an overlay-global priority (SteamVR's experimental "Enable global input from overlays"), and only outside games unless In games is on. Frametop Input Settings gets a Controllers page to map them. Gaze mode: a left press while the gaze has the pointer isn't sent at once. The pointer stops, you drag it onto what you meant with the button held, and the release clicks there; a press held still for POINTER_GAZE_HOLD (0.5 s) becomes a real press, for drags. The dot shows only while the mouse moves it (POINTER_GAZE_SHOW), while a press is held, and as a pulse per click. Outside games the pointer stays on while gaze mode is on. ft-gazed takes a third of each lesson's offset instead of all of it: in the first live test one 6 degree lesson moved everything and put the next target 7 degrees off. Frametop Input Settings gets a Gaze page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
10f21273a6 |
Add gaze mode to the pointer: it goes where you look
MAGIC pointing (Zhai et al. 1999), off by default: with POINTER_GAZE=1, "gaze on", or a button mapped to the new gaze_toggle action, the cursor ray is the corrected gaze from ft-gazed while the gaze has the pointer. Moving the mouse takes the pointer from where the gaze left it; looking more than POINTER_GAZE_RETAKE (5 deg) away with the mouse still gives it back. The pointer is aimed at the gaze, never steered toward it, and only fresh gaze moves it, so a stopped service or a blink leaves it where it is. A mouse nudge of up to POINTER_GAZE_NUDGE_MAX (8 deg) before a click is sent to ft-gazed as a lesson in the eye tracker's error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cfe5990b12 |
Mark head follow as experimental
It works but is only lightly tested, and what's left is mostly tuning its settings. Frametop Input Settings now says so under the Head follow switch, and the README, design notes, and button action name say it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9addd8c669 |
Move the head-follow cursor only after the head passes the leash
Easing the reference toward the head all the time moved the cursor on every small head movement, so a cursor parked in a corner of the view drifted. Now nothing moves while the head stays within the leash. Once the head has been past it for POINTER_LEASH_DELAY (0.2 s, so a glance out and back doesn't count), the reference eases all the way to where you face (POINTER_LEASH_RETURN) and the cursor lands back in its place in the view, then the leash waits again. Leaning inside the leash no longer moves the cursor either. Leash 0 stays head-locked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9f4b61729d |
Settle head follow back on where you look; let the mouse reach 70 degrees
The leash only moved its reference when the head reached the leash's end, so after turning back it sat up to the leash off, and recentring meant overshooting with the head. The reference now eases toward the head's facing (POINTER_LEASH_RETURN, 0.2 s) and never lags more than the leash, so the cursor settles back to its place in the view once the head stops. The mouse can now move the cursor up to POINTER_FOLLOW_REACH (70 degrees) from the middle of the view, up from a fixed 40. Both are sliders on the Pointer page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
137705cbfd |
Add head follow: the pointer comes along when you turn your head
Off by default. With POINTER_FOLLOW=1 (a switch on the Pointer page of Frametop Input Settings, or a mouse button mapped to Head follow on/off), the cursor rides on a reference direction leashed POINTER_LEASH_DEG (10) from where you face. Within the leash it stays put in the room; past it, it turns with your head and keeps its offset. A leash of 0 locks it to your view. Head roll is ignored, the cursor stays within 40 degrees of the reference, and it holds still in the room while the left button is down so the head can't nudge a click or a drag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8f051db083 |
Send typing to the panel clicked last
An ungrabbed keyboard reached both sides at once: gamescope, which in VR reads every input device itself (the SteamOS build's InputStealer), typed into its focused app, and ft-screens typed into the desktop. Space in the desktop paused Spotify on the dashboard, including every space frame-voice dictated. And while the SteamVR dashboard was open, the desktop got no keys at all. Typing now follows the last click. ft-screens sees clicks on its own screens; the pointer helper reports the panel under the dot on each mouse press, so a click on any other panel sends typing to Steam. While typing goes to the desktop and the screens are showing, ft-screens tells the relay every second, and the relay grabs pass-through keyboards. A grab waits until no key is down, and the relay lets go if ft-screens stops reporting. Controller clicks on other panels aren't visible to overlay apps, so they don't move typing (noted in the README). A program that reads every keyboard for a hotkey loses a grabbed one. Repeating the keys on another input device doesn't work, since gamescope reads that too, so with SHARE_KEYS=1 the relay sends them to @frametop_keys as datagrams with the device name. It's off by default: any local process that binds that name first would get every key typed into the desktop. The docs now say gamescope reads keyboards and the headset's buttons itself; they said SteamVR passed keys on to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
705b434cf3 |
Take over the volume keys so they can't crash gamescope
gamescope sends volume up and down to Steam by moving keyboard focus to Steam for the key and back. When nothing had focus, it moves focus back to null and wlroots aborts, which ends the whole VR session. One press of the headset's volume button did that while working in the Frametop desktop. The input relay now handles volume keys from every device that has them, the headset's buttons included, and steps the default output with wpctl (5%, repeating while held). SteamVR, which passes keys on to gamescope, never sees a volume key: devices with a keymap (gpio-keys, USB and Bluetooth keyboards) get only their volume entries remapped to unused codes, so the headset's click button keeps working, and pmic_resin, which has only volume down, is grabbed. The keymaps go back when the relay stops, and --no-grab leaves the volume keys alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f70073b140 |
Leave an unchanged SteamVR driver in place when reinstalling
Re-running the installer replaced the ft_pointer driver's files under a running SteamVR. SteamVR then kept giving the virtual controller its hand role but ignored its laser claim, so the mouse moved its dot without SteamVR's laser until SteamVR restarted. The driver is now staged and only replaced when it changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
54b1e5cd4b |
Aim controller rays along the laser, not the controller
The Frame controllers' laser comes from their render model's tip, 40 degrees below the controller pose's forward axis. ft-screens tested rays along the pose, so a controller's laser near a screen's controls didn't reveal them, and dragging the resize tab or the roll knob with a controller followed the wrong point. Rays now start from the tip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bc74097331 |
Fix the installer; start the desktop without the Steam client's environment
install.sh: a comment with an apostrophe inside a single-quoted command (from the distrobox pin) ended the quote and broke the script at step 1. The VR launcher started the desktop with the Steam client's environment, including LD_LIBRARY_PATH to Steam's runtime, whose libavcodec has no H.264 decoder: VLC in the desktop couldn't play most videos. The session script now drops the client's variables before starting anything, so apps use the system's libraries and codecs. The README and the installer now recommend setting Steam's Sleep after (When Plugged In and Idle) to Never; Steam suspends the Frame after an hour otherwise, even while charging. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b0036cdeae |
Rewrite the README and docs in plainer prose
The README drops the bold headlines on every item and reads as prose. The reference is brought up to date and loses its bold labels. The design doc is reorganized by topic instead of as a dated work log, keeping the technical findings. The setup guide uses subheadings for troubleshooting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4d845492e7 |
Let the headset sleep with the helper idle; keep the container out of our units
With the pointer released, the helper still listed overlays by running vrcmd every second, a new SteamVR client each time, which kept SteamVR from going to standby. The list now pauses while the pointer is off. Starting the dev container from one of our services made that service own it, so stopping the service stopped the container and the desktop's compositor with it. scripts/container-up.sh starts the container in a systemd scope of its own before every distrobox enter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6122313fae |
Record the screens-over-games test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d439bc3f25 |
Frametop: a multi-screen desktop and universal 3D mouse for the Steam Frame
Several KDE Plasma screens floating in SteamVR, each a real monitor of any resolution and shape, shown by our own compositor (ft-screens), with a layout, wrist pinning, and visibility modes; a Bluetooth mouse that drives all of SteamVR as a room-anchored 3D pointer (input relay, ft-pointer helper, ft_pointer SteamVR driver); two settings apps; and Bluetooth LE fixes. Installs on the headset with ./install.sh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |