A one-line installer, and gaze mode in install.sh

- 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>
This commit is contained in:
DeeJanuzandClaude Opus 5.5 committed 2026-10-01 15:04:49 -06:00
1 parent 0280e7441d
commit c257000a23
7 files changed
+164 -25

No files matched your search

+14 -9
View File
@@ -18,15 +18,15 @@ You need a Steam Frame with an internet connection, a keyboard (Bluetooth, or th
1. In the launcher, choose Launch a program → Desktop.
2. In the application menu, open System → Konsole.
3. Clone the repo and run the installer:
3. Run:
```
git clone https://github.com/DeeJanuz/frametop.git ~/frametop
cd ~/frametop
./install.sh
curl -fsSL https://raw.githubusercontent.com/DeeJanuz/frametop/main/get.sh | bash
```
The installer sets up distrobox in your home folder (the system files aren't touched), a Fedora build container, and everything else. The first run downloads 1–2 GB. It asks you three things along the way: whether to install the Bluetooth fixes, whether to install hand tracking (experimental), and whether to restart SteamVR. The Bluetooth fixes and hand tracking need your `sudo` password; if you've never set one, run `passwd` first, or skip them for now. SteamVR has to restart once at the end, which closes everything open in VR, including the terminal. Rebooting the headset works too.
It asks which version you want: stable (the `main` branch, tested releases) or experimental (the `experimental` branch, the newest features, less tested). Then it clones the repo into `~/frametop` and runs `install.sh`. To choose without the question, add `-s -- --stable` or `-s -- --experimental` after `bash`. By hand, the same is `git clone https://github.com/DeeJanuz/frametop.git ~/frametop`, then `cd ~/frametop` and `./install.sh` (add `--branch experimental` to the clone for experimental).
The installer sets up distrobox in your home folder (the system files aren't touched), a Fedora build container, and everything else. The first run downloads 1–2 GB. It asks you four things along the way: whether to install gaze mode (experimental, yes by default), the Bluetooth fixes, and hand tracking (experimental), then whether to restart SteamVR. The Bluetooth fixes and hand tracking need your `sudo` password; if you've never set one, run `passwd` first, or skip them for now. SteamVR has to restart once at the end, which closes everything open in VR, including the terminal. Rebooting the headset works too.
After the restart, Launch a program → Desktop opens the multi-screen desktop, with its screens arranged around where you're facing. Frametop Display Settings and Frametop Input Settings are in the desktop's application menu, under Settings.
@@ -68,7 +68,7 @@ Restarting the desktop (Restart desktop in Frametop Display Settings) closes its
### Experimental: gaze and hand tracking
Gaze mode makes the pointer go where you look. The installer doesn't set it up: run `gaze/run.sh install`, then turn it on and calibrate on the Gaze page of Frametop Input Settings. Meta+J left-clicks and Meta+K right-clicks where you look; hold the key and turn your head to correct the aim, then let go. The mouse's buttons work the same way, with the mouse doing the correcting, and the corrections teach the gaze tracker.
Gaze mode makes the pointer go where you look. The installer offers it (or run `gaze/run.sh install` later); turn it on and calibrate on the Gaze page of Frametop Input Settings. Meta+J left-clicks and Meta+K right-clicks where you look; hold the key and turn your head to correct the aim, then let go. The mouse's buttons work the same way, with the mouse doing the correcting, and the corrections teach the gaze tracker.
Hand tracking, which the installer offers, shows your hands through the screens. Turn it on with `ft-handsctl on` and off with `ft-handsctl off`. With `POINTER_HANDS=1` in `~/.config/frametop.conf`, a pinch clicks and a grip drags. [docs/reference.md](docs/reference.md) has the details of both.
@@ -107,10 +107,14 @@ This writes `frametop-report-<date>.txt` with version numbers, service states, s
## Update
Run the same command again. It updates `~/frametop` to the latest of the version you have (or switches, if you pick the other one) and installs it:
```
cd ~/frametop && git pull && ./install.sh
curl -fsSL https://raw.githubusercontent.com/DeeJanuz/frametop/main/get.sh | bash
```
Or by hand: `cd ~/frametop && git pull && ./install.sh`.
## Uninstall
```
@@ -135,6 +139,7 @@ A Plasma session runs nested inside ft-screens (`screens/`), a small Wayland com
| Folder | What it is |
| --- | --- |
| `get.sh` | The one-line installer: picks stable or experimental, clones or updates the repo, and runs `install.sh`. |
| `install.sh` | The one-step installer. Safe to re-run. |
| `desktops.sh` | Start, stop, and configure the desktop, and install the input relay. |
| `screens/` | ft-screens, the compositor (wlroots and OpenVR). |
@@ -166,13 +171,13 @@ The scripts also work from a Linux or WSL PC over SSH, which is easier for editi
IdentityFile ~/.ssh/<your-key>
```
3. The Bluetooth fixes need `sudo`, and there's no terminal on the Frame to type the password into, so put it in `.env` at the repo root. It's gitignored and never synced:
3. The Bluetooth fixes and hand tracking need `sudo` on the Frame. The installer asks for the password in your terminal (over `ssh -t`). To skip the question, or to install with no terminal, put it in `.env` at the repo root instead. It's gitignored and never synced:
```
steamos_root_pwd="<password>"
```
Then run `./install.sh` from the PC. Daily use:
Then run `./install.sh` from the PC. If SteamVR isn't running on the Frame, the services that need it start with it later. Daily use:
```
scripts/doctor.sh # is the Frame reachable and ready?
+1 -1
View File
@@ -199,7 +199,7 @@ power/run.sh log
## Gaze pointer (experimental)
In gaze mode the 3D mouse's pointer goes where you look, and the mouse or the keyboard does the last bit. It needs the gaze service, which `install.sh` doesn't install: `gaze/run.sh install` builds it and runs `gaze/ft-gazed` as `frametop-gaze.service`, which starts with SteamVR. Turn gaze mode on with the Gaze page of Frametop Input Settings, `gaze/ft-gazectl on`, `POINTER_GAZE=1`, or a button or key combination mapped to Gaze pointer on/off.
In gaze mode the 3D mouse's pointer goes where you look, and the mouse or the keyboard does the last bit. It needs the gaze service, which `install.sh` offers (yes by default) and `gaze/run.sh install` installs on its own: it builds it and runs `gaze/ft-gazed` as `frametop-gaze.service`, which starts with SteamVR. Turn gaze mode on with the Gaze page of Frametop Input Settings, `gaze/ft-gazectl on`, `POINTER_GAZE=1`, or a button or key combination mapped to Gaze pointer on/off.
- Meta+J left-clicks and Meta+K right-clicks where you look. A quick tap clicks where the dot was at the press. Hold instead, and the dot stays put in your view: turn your head until it's on what you meant, and let go to click there. Held still for `POINTER_GAZE_HOLD` (0.5 s), the press becomes a real one, and your head drags. Meta+K with Meta+J held presses where the dot is now, to drag from there, and a second Meta+K during that drag (a double Meta+K) pans and tilts what you're dragging while it's held.
- The mouse's buttons work the same way, with the mouse steering instead of your head (`POINTER_GAZE_MOUSE=precision`, the default): the right button with the left held starts a drag, and a double right click pans and tilts what you're dragging. With `POINTER_GAZE_MOUSE_MOVE=held`, the default, the mouse only corrects: while the gaze has the pointer, moving it does nothing unless a button is held. `free` lets the mouse take the pointer any time. With the gaze stale for a second, in a game, or with the headset off, the mouse works as usual.
+1 -1
View File
@@ -7,7 +7,7 @@ The Steam Frame's eye tracking as pointer input: a gaze mode for the 3D mouse (t
- `tracker/` is our own eye tracker, an alternative to SteamVR's: `ft-eyes` finds the pupils and glints in the eye-camera frames that `ft-eyegrab` (a small root service) copies out of SteamVR's tracker. See "Our own eye tracker" below.
- `probe/ft-gazeprobe` (GTK 4, host Python) is a fullscreen playground, for developing the gaze tracking: day to day, the calibration and the checks run in the headset panel (Quick check, Calibrate, and Check headset fit on the Gaze page). It runs ft-gaze, draws where you're looking, measures accuracy, and tries out hold-to-adjust clicking with a calibration that learns from your adjustments.
Day to day, install the gaze service, then turn gaze mode on and calibrate on the Gaze page of Frametop Input Settings (Calibrate). The installer doesn't install any of this.
Day to day, install the gaze service, then turn gaze mode on and calibrate on the Gaze page of Frametop Input Settings (Calibrate). The installer offers the gaze service (`gaze/run.sh install`); the probe and our own tracker are installed by hand.
```
gaze/run.sh install # the gaze service: builds ft-gaze and the panel, starts with SteamVR
Executable
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env bash
# Frametop's one-line installer. In a terminal on the Steam Frame (Konsole in the desktop, or
# over SSH):
#
# curl -fsSL https://raw.githubusercontent.com/DeeJanuz/frametop/main/get.sh | bash
#
# It asks which version to install, clones the repo into ~/frametop (or updates the clone
# that's there), and runs its install.sh. Run it again to update, or to switch versions.
# Options (piped, they go after "bash -s --"):
# --stable the main branch: tested releases (the default for a new install)
# --experimental the experimental branch: the newest features, less tested
# --dir DIR where the repo goes (default ~/frametop)
# --clone-only get or update the repo, but don't run install.sh
# --yes, --no-bluetooth passed to install.sh (--yes also answers this script's question:
# the version already there, or stable)
set -euo pipefail
usage() {
cat <<'EOF'
usage: get.sh [--stable | --experimental] [--dir DIR] [--clone-only] [--yes] [--no-bluetooth]
piped: curl -fsSL https://raw.githubusercontent.com/DeeJanuz/frametop/main/get.sh | bash -s -- [options]
EOF
}
# Everything happens in main, called on the last line, so a download cut short runs nothing.
main() {
local repo=https://github.com/DeeJanuz/frametop.git dir=$HOME/frametop branch= clone_only=0
local yes=0 tty=0 current= def answer
local pass=()
while [ $# -gt 0 ]; do
case $1 in
--stable) branch=main ;;
--experimental) branch=experimental ;;
--dir) dir=${2:?--dir needs a folder}; shift ;;
--clone-only) clone_only=1 ;;
--yes) yes=1; pass+=("$1") ;;
--no-bluetooth) pass+=("$1") ;;
-h|--help) usage; return 0 ;;
*) echo "unknown option: $1" >&2; usage >&2; return 2 ;;
esac
shift
done
if ! { grep -qx 'ID=steamos' /etc/os-release && grep -qE '^VARIANT_ID="?vr"?$' /etc/os-release; } 2>/dev/null; then
echo "Frametop installs on a Steam Frame (SteamOS, VR variant). Run this in a terminal on the headset." >&2
return 1
fi
# Piped into bash, stdin is this script: the questions (here and install.sh's) read the terminal.
{ : </dev/tty; } 2>/dev/null && tty=1
if [ "$tty" = 0 ] && [ "$yes" = 0 ]; then
echo "This asks questions, and there's no terminal to ask in: run it in one, or add --yes." >&2
return 1
fi
if [ -e "$dir/.git" ]; then
git -C "$dir" remote get-url origin 2>/dev/null | grep -qi 'frametop' ||
{ echo "$dir is a git repo, but not Frametop's. Pick another folder with --dir." >&2; return 1; }
current=$(git -C "$dir" branch --show-current)
elif [ -e "$dir" ]; then
echo "$dir is there and isn't Frametop's repo. Move it, or pick another folder with --dir." >&2
return 1
fi
if [ -z "$branch" ]; then
def=main
[ "$current" = experimental ] && def=experimental
if [ "$yes" = 1 ]; then
branch=$def
else
echo "Which version of Frametop?"
echo " 1) stable: the main branch, tested releases"
echo " 2) experimental: the newest features, less tested"
[ -n "$current" ] && echo "(installed now: $current)"
read -r -p "Choose 1 or 2 [$([ "$def" = main ] && echo 1 || echo 2)]: " answer </dev/tty || answer=
case ${answer:-$def} in
1|main|s*) branch=main ;;
2|experimental|e*) branch=experimental ;;
*) echo "not 1 or 2: $answer" >&2; return 2 ;;
esac
fi
fi
if [ ! -e "$dir" ]; then
echo "Cloning Frametop ($branch) into $dir"
git clone --branch "$branch" "$repo" "$dir"
else
if ! git -C "$dir" diff --quiet || ! git -C "$dir" diff --cached --quiet; then
echo "$dir has changes of its own. Commit or stash them first (git -C $dir status)." >&2
return 1
fi
echo "Updating $dir to the latest $branch"
git -C "$dir" fetch --quiet origin
if [ "$current" != "$branch" ]; then
if git -C "$dir" show-ref --verify --quiet "refs/heads/$branch"; then
git -C "$dir" switch --quiet "$branch"
else
git -C "$dir" switch --quiet --track -c "$branch" "origin/$branch"
fi
fi
git -C "$dir" merge --ff-only --quiet "origin/$branch" ||
{ echo "$dir has commits of its own on $branch, so it can't just move to the latest. Update it by hand." >&2; return 1; }
fi
echo "Frametop $branch: $(git -C "$dir" log -1 --format='%h %s')"
if [ "$clone_only" = 1 ]; then
echo "Install with: cd $dir && ./install.sh"
return 0
fi
cd "$dir"
if [ "$tty" = 1 ]; then
./install.sh "${pass[@]}" </dev/tty
else
./install.sh "${pass[@]}" </dev/null
fi
}
main "$@"
+4
View File
@@ -826,6 +826,10 @@ class Backend(QObject):
def gazeServiceRunning(self):
return bool(self._gaze)
@Property(bool, notify=gazeChanged)
def gazeServiceInstalled(self):
return os.path.exists(os.path.expanduser("~/.config/systemd/user/frametop-gaze.service"))
@Property(int, notify=gazeChanged)
def gazeMode(self):
"""The helper's gaze mode now: 1 on, 0 off, -1 no answer (helper not running)."""
+2 -1
View File
@@ -1018,7 +1018,8 @@ Kirigami.ApplicationWindow {
Kirigami.FormData.label: "Service:"
text: backend.gazeServiceRunning
? (gpage.status.ft_gaze ? "running" : "running, eye tracker reader restarting")
: "not running (frametop-gaze.service, starts with SteamVR)"
: backend.gazeServiceInstalled ? "not running (frametop-gaze.service, starts with SteamVR)"
: "not installed: run gaze/run.sh install in the Frametop folder, in a terminal"
color: backend.gazeServiceRunning ? Kirigami.Theme.textColor : Kirigami.Theme.negativeTextColor
}
Controls.Label {
+25 -13
View File
@@ -1,12 +1,13 @@
#!/usr/bin/env bash
# Install everything on the Steam Frame: the build container, Frametop (multi-screen
# desktop, input relay, universal 3D mouse, settings app), and optionally the Bluetooth
# fixes and hand tracking. Run it on the headset in a terminal, from this repo. It's safe
# to re-run, for example after `git pull`.
# desktop, input relay, universal 3D mouse, settings app), and optionally gaze mode, the
# Bluetooth fixes, and hand tracking. Run it on the headset in a terminal, from this repo.
# It's safe to re-run, for example after `git pull`.
# (It also works from a PC over SSH; see "Developing from a PC" in the README.)
#
# Usage: ./install.sh [--yes] [--no-bluetooth]
# --yes don't ask; skips the Bluetooth fixes and the SteamVR restart
# --yes don't ask; installs gaze mode, skips the Bluetooth fixes, hand tracking,
# and the SteamVR restart
# --no-bluetooth don't offer the Bluetooth fixes
set -euo pipefail
@@ -44,8 +45,12 @@ else
echo "Installing on $FRAME_HOST over SSH (repo copy at $FRAME_REPO)"
"$root/scripts/sync.sh" >/dev/null
fi
if [ "$FRAME_LOCAL" = 0 ] || [ -n "${SSH_CONNECTION:-}" ]; then
echo "Over SSH: if the connection drops, run this again (it keeps what it downloaded). Services"
echo "that need SteamVR start with it if it isn't running now."
fi
step "1/9 distrobox (container tool, installed in your home folder)"
step "1/10 distrobox (container tool, installed in your home folder)"
if on_frame 'test -x ~/.local/bin/distrobox'; then
echo "already installed: $(on_frame '~/.local/bin/distrobox version | head -1')"
else
@@ -55,25 +60,25 @@ else
cd ~/dev/src/distrobox && ./install --prefix ~/.local'
fi
step "2/9 build container (Fedora 44 'dev', about 1-2 GB the first time)"
step "2/10 build container (Fedora 44 'dev', about 1-2 GB the first time)"
"$root/setup/dev-container.sh"
step "3/9 input relay (keeps Bluetooth mice working in SteamVR, device roles, button maps)"
step "3/10 input relay (keeps Bluetooth mice working in SteamVR, device roles, button maps)"
"$root/desktops.sh" relay install
step "4/9 3D mouse: SteamVR driver"
step "4/10 3D mouse: SteamVR driver"
"$root/pointer/driver/build.sh"
"$root/pointer/driver/install.sh" install 2>&1 | grep -v xdg-open
step "5/9 3D mouse: pointer helper service"
step "5/10 3D mouse: pointer helper service"
"$root/pointer/helper/build.sh"
"$root/pointer/helper/run.sh" install
step "6/9 power service (turns the displays off while the headset isn't used, even on a stand)"
step "6/10 power service (turns the displays off while the headset isn't used, even on a stand)"
"$root/power/build.sh"
"$root/power/run.sh" install
step "7/9 multi-screen desktop (ft-screens), Frametop Input Settings, and Frametop Display Settings"
step "7/10 multi-screen desktop (ft-screens), Frametop Input Settings, and Frametop Display Settings"
"$root/screens/build.sh"
"$root/desktops.sh" install >/dev/null
"$root/input-settings/install.sh"
@@ -82,14 +87,21 @@ step "7/9 multi-screen desktop (ft-screens), Frametop Input Settings, and Framet
on_frame "sed -i 's/^POINTER=0/POINTER=1/' ~/.config/frametop.conf; grep -q '^POINTER=' ~/.config/frametop.conf || echo 'POINTER=1' >> ~/.config/frametop.conf"
echo "the launcher's Desktop entry now opens the multi-screen desktop; 3D mouse on (POINTER=1 in ~/.config/frametop.conf)"
step "8/9 Bluetooth fixes (optional; they let LE mice and keyboards like the Swiftpoint Z3 reconnect)"
step "8/10 gaze mode (optional, experimental: the pointer goes where you look)"
if ask "Install gaze mode? You turn it on and calibrate it in Frametop Input Settings, on the Gaze page." y; then
"$root/gaze/run.sh" install
else
echo "skipped. Install later with: gaze/run.sh install"
fi
step "9/10 Bluetooth fixes (optional; they let LE mice and keyboards like the Swiftpoint Z3 reconnect)"
if [ "$bluetooth" = 1 ] && ask "Install the Bluetooth fixes? They need your password (sudo)." n; then
"$root/setup/bluetooth/install.sh" install
else
echo "skipped. Install later with: setup/bluetooth/install.sh install"
fi
step "9/9 hand tracking (optional, experimental: your hands show over the screens)"
step "10/10 hand tracking (optional, experimental: your hands show over the screens)"
if ask "Install hand tracking? It needs your password (sudo) to let its camera service read the headset cameras." n; then
"$root/hands/run.sh" install
else