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>
- install.sh no longer offers hand tracking (it's heavy on the CPU and
needs more work); hands/ still builds and installs by hand
- the build container drops python3-opencv and python3-numpy, which
only hand tracking's tools used: Fedora's OpenCV pulls in over a GB
- README: a new opening and feature list, the Use section by topic
(screens, mouse, floating windows, profiles, gaze), and new known
limitations
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- get.sh: curl ... | bash asks for stable (main) or experimental,
clones or updates ~/frametop, and runs install.sh; run it again to
update or switch
- install.sh offers gaze mode (step 8, yes by default) and notes what
to do if an SSH connection drops
- Input Settings' Gaze page says when the gaze service isn't installed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
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>
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>
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>
A profile is a named layout plus the screens it hides and its apps' windows
(docs/profiles.md). ft-layout save captures them (ft-floatd's "windows",
after the KWin script reports every window as it is now); use opens them:
the screens move, open windows of each app go to their places (on a screen,
maximized or not, or floating), and missing apps start, once and then again
for each window still missing 3 s after the first. Nothing closes.
The desktop starts in FT_PROFILE or default_profile (ft-layout start, from
the session script). Each profile gets a launcher entry (Frametop: NAME, in
SteamVR's Launch a program list) that switches to it or starts the desktop
in it. The relay's profile:NAME action and Input Settings' "Open profile"
entries put one on a key, mouse button, or controller button. Display
Settings' Layout page becomes Layout & profiles: Save as profile, Open
profile, the profile's apps, and Start in profile. Plasma's own session
restore is off in the Frametop session.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ft-screens: "conceal <screen|all>" hides a screen on its own, whatever the
visibility mode or the hotkey says, until "reveal"; "concealed" lists them.
(Not "hide N": older builds read anything starting with "hide" as the hotkey.)
ft-layout keeps it per screen ("hidden" in the layout), applies it when it
arranges the screens, and has hide/show N|all and hidden. Display Settings
gets a Shown switch per screen on the Visibility tab. ft-floatd floats a new
window that opens on a hidden screen, and puts a stray window on a screen
that shows. Profiles (next) use it to show only some screens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
krdp streams every screen, so the VNC screen is now the primary's size and the
FreeRDP window is shifted so the primary fills it (ft-layout remote-view gives
the offset). It resizes and reconnects when the layout changes. With remote
access on, KWin's D-Bus screenshot interface is open too, for scripts that look
at the screens without the headset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 6f5a23c80f2ecd26e6a96c86b102be36d518ee25)
Hand tracking (ft-camd, ft-hands, and their tools) joins the desktop. The
hands file and ring move to /run/user/UID/frametop-hands/, which ft-screens'
hand cutouts now read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The runtime files move to /run/user/UID/frametop-hands/: the desktop
session deletes /run/user/UID/frametop at every start.
- The cutout copy shader runs at highp: mediump (16-bit on Adreno)
stepped 1.7 texels across a 3440-pixel screen.
- One hand no longer pinches both sides after its left/right call flips
mid-pinch, and --pinch-palm-down (0.6) holds back pinches with the palm
facing down (typing on a lap keyboard).
- ft-camd judges a colour frame fresh by its luma rows only, and logs
per-buffer changes at stale colour frames with FT_CAMD_DEBUG=1.
- hands/run.sh caps skips the setcap when ft-camd already has them.
- The replay tool dumps poses (--poses), and its pinch events carry the
hand id.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
- Programs: ft-camd (the camera broker), ft-hands (the tracker), and
ft-handreplay and ft-ringplay for recordings, built by hands/build.sh
into hands/build/ with one Makefile. The first build fetches ncnn at
frame-hands' pinned tag and builds it with the same options.
- ft-camd gets its privileges from file capabilities (CAP_SYS_PTRACE,
CAP_PERFMON, CAP_DAC_READ_SEARCH) that hands/run.sh install sets with
sudo, and drops them once set up. It still works under sudo. It runs
on the host, linked statically, as frametop-camd.service. ft-hands
runs in the dev container as frametop-hands.service. Both start and
stop with SteamVR.
- Files move to /run/user/UID/frametop/ (cam-ring, hands, gestures),
not $XDG_RUNTIME_DIR, which a terminal in the Frametop desktop has
its own of. SIGUSR1 recordings go to ~/.local/share/frametop/hands.
- The calibration is read through /run/host in the container.
- Settings: HANDS_SWAP_SIDES and HANDS_CPUS in frametop.conf.
- install.sh offers hand tracking as an optional last step.
- The container gets jsoncpp-devel, glibc-static, and NumPy and OpenCV
for the Python tools.
- tools/ring.py reads the ring, and models/NOTICE credits the
Apache-2.0 models.
Checked: ft-handreplay gives identical summaries and byte-identical
depth dumps to frame-hands' fh-replay on both 2026-09-29 recordings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Frametop Input Settings' Gaze page gets two settings, saved in frametop.conf and
read again by ft-gazed when the file changes: Eye tracker (GAZE_TRACKER: SteamVR's
or our own, frame-eyes' fe-trackd) and Eye bias (GAZE_EYE: auto, left, right).
ft-gazed now combines the eyes, each calibrated on its own: SteamVR's set 2 eyes
with the probe's Left eye and Right eye calibrations, or our tracker's eyes as
they come. Without per-eye calibrations, or with --source, it keeps the older
one-source path. A pointer nudge finds its look from the raw gaze the helper
echoes back; with our tracker it goes to fe-trackd as a click.
The bias leans instead of choosing (gazecal.EyeWeights): on 306 live clicks the
eyes' errors partly cancelled, both together 0.65 deg off against 0.96 and 1.11
for either alone. Left or Right counts that eye twice; auto weights each eye by
its RMS miss at its last 20 nudges, since the calibration's fit picked the wrong
eye on SteamVR's test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
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>
With a screen's scale set to anything but 100%, clicks landed away from
the cursor, further off the further from the top left (#3). ft-screens
hands KWin panel positions in buffer pixels, and KWin's nested Wayland
backend (6.2.5, WaylandInputDevice) adds surface coordinates to its
output's logical position without dividing by the output's scale. At
125% a click at the middle of a 3440x1440 screen, (1720, 720), reached
KWin as logical (1720, 720), pixel (2150, 900).
ft-screens now keeps a scale per screen and divides pointer positions by
it. ft-layout sends each screen's scale, as KWin reports it after
applying, with a new "scale N s" command, whenever it applies scales:
at desktop start and from Frametop Display Settings. Screens default to
1, so an ft-layout that never sends it keeps the old behaviour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
SteamVR's "Enable global input from overlays (Experimental)" could only
be turned on from the settings app: its notice, with the button, hid once
the setting was on. It's now a switch, and the notice shows only when
buttons are mapped and the setting is off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
KWin's output positions now follow where the Frametop screens are in the room
instead of their numbers: a screen you see to the left of another is to its
left in Plasma, so the pointer and dragged windows cross straight to it.
Screens one above the other stack, and wrist-pinned screens go last.
ft-layout scale works this out from ft-screens' head pose and screen poses,
and runs after arranging, capturing, or pinning; ft-screens runs it half a
second after a screen is let go. With no head pose (headset off) KWin's
order is kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
A Meta tap on a pass-through keyboard toggled the SteamVR dashboard, which
got in the way of using Meta on its own. It's now off unless
META_DASHBOARD=1 is in ~/.config/frametop.conf. Meta as a modifier
(Meta+Shift+R, Meta+Shift+H) is unaffected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hidden controls were taken out of SteamVR, so only ft-screens' own
proximity test could bring them back, and that test didn't match every
controller's laser. They now stay in place fully transparent while
hidden, and SteamVR's hover event on one reveals them, for any laser.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
A new setting, During VR games: with Always, the screens hide while a VR
game runs and show while the SteamVR dashboard is open (the default), or
stay visible over the game. The hotkey still shows them; a game starting or
stopping resets it. Socket command: ingames hide|visible.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Visible screens kept SteamVR's laser mouse on, which takes the controllers
away from a VR game. Now, by default, that's off while a scene app runs:
the screens stay over the game and the 3D mouse or the dashboard works
them. Frametop Display Settings has the choice (always, except during VR
games, only with the dashboard open); the socket command is controllers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>