Commit Graph
122 Commits
Author SHA1 Message Date
DeeJanuzandClaude Opus 5.5 9029a2d7a5 hands: drawn hands blend between frames, hold still when lost, refuse jumps
User, 2026-10-13: "implement interpolation for the rendered hands so that they
feel smoother", and "if the hand is lost it's preferable the rendered hand
display freeze instead of make impossible movements, spaz out, or vanish until a
few seconds have passed".

screens/handhold.h, per side in ft-handview:
- each tracking frame blends in joint by joint from the drawn pose over about
  the frame interval (15-70 ms), drawn every 15 ms meanwhile;
- a lost hand stays frozen where it was drawn for HANDS_VIEW_HOLD s (3);
- a frame 15 cm / 6 m/s off the last one taken, or with the palm turned over,
  waits for two agreeing frames (or 0.25 s) while the drawn hand holds still;
  a first-seen or returning hand waits for two agreeing frames too.
HANDS_VIEW_SMOOTH=0 gives the old behaviour. Tests in handview_test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:30:41 -06:00
DeeJanuzandClaude Opus 5.5 09ba8afcf0 hands: the lazy susan by hand is a little harder again
User, 2026-10-13: "make the lazy susan just slightly more difficult to trigger".
One hand: edge 0.88, lead 0.16, 20 cm, quick at 0.75 m/s somewhere along it;
3 frames off shape ridden out. The flags replayed: all 47 practice strokes still
count (each peaked at 0.8 m/s or more), other use 3 false strokes, down from 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:15:36 -06:00
DeeJanuzandClaude Opus 5.5 e4bc1fbc6e hands: grab that and its glow aim where the open palm faces
The line from the eyes through the palm lit panels off to the side of a palm
that wasn't facing them. The line is now the palm's own, along its normal,
with the margins seen from the palm. HANDS_GRAB_THAT_AIM=eyes brings the old
line back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:36:01 -06:00
DeeJanuzandClaude Opus 5.5 0abcda6e80 float: 16 floating windows by default, up from 8
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:27:33 -06:00
DeeJanuzandClaude Opus 5.5 870ad7687c Merge branch 'hands-fistguard' into experimental
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:32:45 -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 1faa0241e2 Hands: easier pinch throws; sent away, a panel keeps its distance and size; reset on a tap; glow under hand rays only
Throws: a carry's let-go takes the fastest outward stretch in the last
150 ms before the fingers opened (a hand slows as it lets go), at 0.45 m/s
within 70 deg (was 0.55 within 60, the last 100 ms only). Every let-go
moving 0.3 m/s or more is logged.

Sent away (thrown, flicked, pushed), a panel goes along that line out as
far and as big as it was before a hand or "grab that" took it, not back
to its old spot (layouts are for that). Put down past 1.2 m, that's its
distance from then on; never taken, it flies to 3 m as before. The front
slot's screen leaves the slot the same way.

The bar's buttons sit a button's width apart, and the reset button
resets the layout on a tap (HANDS_RESET_HOLD_MS=0; 500 before).

Grab that's glow: ft_pointer's laser sits on the panel with the cursor
all the time (the 3D mouse, the gaze), so "no glow under a laser" kept
the glow off that panel. The pointer helper now tells ft-screens which
hand rays are up ("ray L R", on change and every second); our laser
counts as one only then. Controllers' lasers count as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:50:10 -06:00
DeeJanuzandClaude Opus 5.5 60ca2fba69 Lazy susan: ultrawides come up to 150 deg wide so their text reads
A 5120x1440 remote in the front slot was held to 100 deg wide (51 px/deg,
hard to read). Panels 3:1 or wider may now go to SCREEN_SPIN_MAX_WIDE_DEG
(150) instead, and their curve may wrap that far round you.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:27:12 -06:00
DeeJanuzandClaude Opus 5.5 80bce4cf66 Gaze + hands: the two-hand drag works like Meta+J/K; held pinches no longer drop on a clock race; easier lazy susan
User 2026-10-11, the second gaze test:
- "hand tracking stopped" ends were an unsigned age: ft-hands publishing just after the
  loop's tnow made it negative, wrapped to 584 years, and ended held pinches, nudges,
  drags and scrolls. Signed now.
- The two-hand drag mirrors the keys: hold one pinch (aim, nudge), pinch with the other:
  the left button goes down where the dot is (as Meta+K with Meta+J held); the drag
  lasts while either pinch holds, and the hand still pinching drags. A second pinch
  during a drag joins it.
- The fast-hand filter refused dozens of still pinches (frame-to-frame palm speed is
  noisy): smoothed, 1.0 m/s.
- A three-finger scroll goes on when ft-hands swaps which finger it calls pinched.
- Lazy susan: counted strokes came at 25-30 cm in 230-470 ms, at both limits: 22 cm,
  quick somewhere along it (0.7 m/s peak) within 0.9 s; looser hand shape. The flags
  replayed: 2 strokes in 45 flags of other use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:13:38 -06:00
DeeJanuzandClaude Opus 5.5 0ce6843338 Front slot: a brought screen stays until you move it
User 2026-10-11: "the lazy susan can be considered to be in use and remain active until I
move it out of its spot manually through either the eye tracking or grabbing it with
hand-tracking". Looking away no longer sends it home (SCREEN_FRONT_AWAY=1 brings that
back, with the keep-by-use); a gaze-pinch drag, a hand grab, a push or grab that
releases it, and the next stroke sends it home.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:17:56 -06:00
DeeJanuzandClaude Opus 5.5 b67c1931c6 ft-hands: settings for how easily a pinch lets go
HANDS_PINCH_END, HANDS_PINCH_DRAG_OPEN, HANDS_PINCH_DRAG_OPEN_S, HANDS_GRIP_TAKEOVER_S
(user 2026-10-11, gaze + hands: pinches released at 4-6 cm, and grips took pinches
130 ms in, while the fingers were still together).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:11:24 -06:00
DeeJanuzandClaude Opus 5.5 0b72710474 Gaze + hands: two-hand drag (hold, tap the other), pinches held through tracking pauses, no clicks from fists or strokes, the pointer hides while typing; the slot keeps only on real use
The user's 2026-10-11 test of gaze + hands:
- Two-hand drag as they meant it: hold one hand's pinch (nudge it into place), tap
  with the other: index presses the left button where the pointer is, middle the
  right; the first hand drags (POINTER_GAZE_TWO_HAND). The tap-first right-drag is
  off by default (it could catch a tap followed by a nudge).
- 9 held pinches ended as "hand tracking stopped (1.5 s)" while ft-hands still held
  them: 4 s with nothing pressed, 2.5 s with a press, and the line now says the file's
  seq and ages to find the cause.
- A gaze pinch held back ends without a click when the hand closes into a fist
  (ft-hands' grip may not take it past 0.1 s now); a pinch begun while the palm
  moves faster than 0.6 m/s (a stroke) doesn't click (POINTER_PINCH_MAX_SPEED).
- Three-finger scroll: 3.5 cm and 0.4 s to join.
- Gaze mode hides the dot and collapses its laser for 1.5 s after each key
  (POINTER_TYPING_HIDE): it sat big and bright on the text being typed.
- The front slot's keep counts clicks, scrolls, drags and keys, not the gaze pointer
  passing over it (every brought screen was kept 3 s in).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:11:24 -06:00
DeeJanuzandClaude Opus 5.5 02f1575c52 Gaze + hands first: right-drag by two taps, three-finger scroll, fist guard on every pinch; the slot lets go to manual controls; palm-only susan; dot-only hand laser
User 2026-10-11, gaze + hands becoming the main input:
- Right-drag: an index tap on one hand, then an index pinch on the other within 1 s,
  holds the right button at the gaze lock; that hand drags until it ends
  (POINTER_GAZE_RIGHT_DRAG). It replaces the chord (POINTER_GAZE_CHORD=0).
- Three-finger scroll: the thumb on both the index and middle tips (3 cm) scrolls where
  you looked as the hand moves, like the ray's middle-pinch stick (POINTER_GAZE_SCROLL).
- The fist guard (a hand closing into a fist: no click) now covers gaze and chord
  pinches too, from each hand's own frame (not the ray's).
- A hand's laser shows only its dot: the beam starts just short of it, as the eye's
  does, held still through a carry (POINTER_RAY_BEAM=0; SteamVR's beam width is one
  global setting it reads only at start).
- The front slot gives a screen up to manual controls: grab that, a push, flick or
  throw, or a carry by hand or laser releases it (it moves like any other and stays
  where you put it); the next stroke puts it home and brings the next.
- The lazy susan counts only palm-led strokes (the return stroke does nothing; the
  other hand palms the other way; HANDS_SWEEP_BACK=1 for both).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:38:29 -06:00
DeeJanuzandClaude Opus 5.5 90114ed043 The front slot: one place in front of you for the lazy susan and grab that
The user's proposals (2026-10-11): the lazy susan and grab that were two systems that
fought over a screen (grab that took one the susan brought: 2.3 m wide at 1.5 m).
Now one front slot, one screen at a time (the one there goes home first):
- reading distance (the lazy susan, 1 m) and arm's reach (grab that), both readable
  (ReadableSize, the same view at either); grab that at the one there pulls it closer;
- a push, flick or throw steps it back: arm's reach -> reading -> home;
- one home per screen: every dismissal returns it there; moved by hand or laser it
  lives where you put it; resized in the slot it keeps that view next time;
- turned away from it goes home, unless used 3 s (kept; SCREEN_FRONT_KEEP_MS);
- a strip of the screens' names, left to right as they live, for 2.5 s after each
  stroke, the one in the slot lit and the next each way marked (keyboard.cpp's font,
  now DrawLabel for other panels too);
- SCREEN_UI_SCALE for a remote display's own UI scale (a Mac's Retina: 2);
- the reset button needs a 0.5 s hold, laser or finger (a stray poke reset the layout;
  HANDS_RESET_HOLD_MS).
grab that's remembered widths, send-away widths and view fit go (the slot sizes it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:15:58 -06:00
DeeJanuzandClaude Opus 5.5 12b8159d4e Lazy susan: readable at 1 m, cycles every screen; the laser only for pinching, none in a fist
Lazy susan (the third live test): a brought screen comes 1 m ahead, as big as its
resolution needs for text to read: its logical pixels (KWin's scale undone, now passed
to vr.cpp) at 26 px/deg, at most 100x55 deg keeping its shape, at least 40 deg wide,
curved round you past 60 deg (grabthat::ReadableSize; SCREEN_SPIN_DISTANCE, _PPD,
_MAX_DEG, _MAX_TALL_DEG). Its curve comes back on the way home. The strokes cycled
104, 1, 104 with three screens up: a screen still flying home read as straight ahead;
each one is now taken where it lives (home, or where it's going).

Accidental clicks: most were pinches with no laser up pressing where the pointer was
last left ("no ray up: a press where the pointer is", a dozen in two minutes). With
gaze off they now only wake the pointer (POINTER_PINCH_NO_RAY=1: the old air mouse).
The hand's laser shows only with the thumb and index within 6 cm (gone past 10;
POINTER_RAY_READY), and a fist drops it: fh_ray's "ready" and "open" tests, the fist
read as the index and the other three both curled under 1.1 (FistCurl; measured on 47k
flagged hands: the other three alone read as a fist in a fifth of the pinches). A ray
pinch made while the hand closes into a fist doesn't click. Input Settings switches for
all three (POINTER_ ones reload the helper).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:06:15 -06:00
DeeJanuzandClaude Opus 5.5 a546469db6 Lazy susan brings the next screen to you; easier sweeps; grab that fits wide screens to the view
The lazy susan (the sweep, Meta+Alt+Tab) no longer turns the room: the next screen
round, by where each one lives, comes in front of you at a distance it spans at most
60 deg of the view at, and takes the typing. The next stroke sends it home and brings
the one after; pushed, flicked or thrown it goes home; turned away from for 0.6 s it
goes home; moved by hand or laser it stays. SCREEN_SPIN_BRING=0 turns the room again
(Input Settings switch too).

Sweeps were hard to make (counted strokes came at 30-35 cm, just over the 30 asked):
25 cm in 0.5 s, the hand open 1.4, tilted up to ~45 deg off its edge, and hovering near
a screen counts (the brought one sits right there). The flags replayed: one stroke in
49 flags of other use.

Grab that brought a remote display back at the 1.62 m it was resized to, 0.45 m ahead:
it filled the view and every poke went through it (both flags of the second live test).
A wide one now comes as far out as it spans at most 75 deg (HANDS_GRAB_THAT_VIEW_DEG).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:01:57 -06:00
DeeJanuzandClaude Opus 5.5 516f2c056d Grab that: take the panel the palm's line passes just under
Fifth live test (2026-10-10 22:55): grab that brought the desktop in, it was
carried and thrown 3 m out, then five gestures at it or the remote displays
logged "no panel there". The open palm is held out below the line of sight,
so the line from the eyes through it runs 12-15 deg under where the head
points (the user's flag 150115). Since the fourth live test the line had to
meet a panel exactly, and a thrown 1.35 m tall desktop 3 m out is only
12.7 deg each way, so the line passed just under it. One of the flag's two
fires misses such a panel that way.

Pick now takes the panel the line meets (unchanged, nearest wins), else the
one whose outline it passes nearest within HANDS_GRAB_THAT_MARGIN_DEG
(6 deg seen from the eyes; 0 = the old rule). The gesture itself is
unchanged, so it stays as hard to trigger, and held, pinned, pressed and
near panels are still never taken. "no panel there" now logs where the line
went relative to the head and the nearest panel it may take, with its angle.

Test: TestGrabThatAfterThrow runs the user's sequence with synthetic hands:
grab that brings the desktop in, a bar carry is thrown out and up at
1.3 m/s, then grab that at it again (missed by 0.8 deg with the old rule,
taken now), a remote display after that, precision cases, and the flag's
two fires (2 of 2 now, 1 with the old rule).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:24:02 -06:00
DeeJanuzandClaude Opus 5.5 1c10e9a717 Docs and settings: two anchors, fist reach, buttons for hands, the soft glows, grab that's targets
The example config gets HANDS_FIST_REACH, HANDS_JOIN_REACH, HANDS_BUTTON_REACH,
HANDS_PINCH_BUTTONS, HANDS_EDGE_GLOW, HANDS_GRAB_THAT_FROM, HANDS_GRAB_THAT_MARGIN and
HANDS_GRAB_THAT_FACING; hands/README.md and docs/reference.md describe them, the white
halo, the corner, edge and button glows, and that near panels go away by push, flick or
throw now, not by grab that. `handinput state` counts joins, pinches that waited to be
fists, and buttons pinched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:52:57 -06:00
DeeJanuzandClaude Opus 5.5 cad02f978c Docs and settings: push, two-hand turns, tracking gates, the target's glow, send-away widths
hands/README.md (near input, the palm menu), docs/reference.md and session/frametop.conf.example:
HANDS_PUSH, HANDS_PUSH_SPEED, HANDS_STRETCH_TURN, HANDS_TRACK_GATE, HANDS_TARGET_GLOW,
HANDS_SEND_AWAY_WIDTH; fists ft-hands calls pinches, the pinch slack, holds through the helper's
word, the near-the-eyes carry, the stricter palm menu, with the replays' numbers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:38:30 -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 8d3de8e13f Merge branch 'hands-gazechord' into hands-next: the gaze chord, hand input switches
Conflicts:
- ft-pointer.cpp, the gaze pinch comment: typing's "the rules for all
  gestures hold (..., typing, ...)" kept, gazechord's chord and
  POINTER_GAZE_PINCH paragraphs added after it, and a line that a hand
  typing holds back makes no chord.
- ft-pointer.cpp, the hands settings: gazechord's gazePinchOn, nearOn,
  handsTrack kept; typingHold dropped for typing's typing::Params.
- ft-pointer.cpp, mayPoint: both conditions (handsOn, and not a side
  typing holds back).
- ft-pointer.cpp, beginGazePinch: gazechord's busy line (a chord isn't
  busy), without the old sinceKey/typingHold lines. The chord's pinch
  goes through typing.h's Why in the gesture loop before startGesture,
  like any pinch, so a hand typing holds back can't make one.
- docs/reference.md settings line: typing's and reach's keys, plus
  POINTER_GAZE_PINCH.
- hands/README.md near input: typing's hold-back sentence and
  gazechord's HANDS_NEAR=on.
- session/frametop.conf.example: reach's HANDS_GRAB_ROTATE* and
  HANDS_GRAB_THAT* lines, then gazechord's HANDS_NEAR line.

Reconciled: gazehand's Situation loses sinceKey and typingHold, and
Refused its "ignored (typing)": the helper had stopped setting them on
hands-typing (typing.h holds hands back first), so that guard could
never fire. gazehand-test's key checks now drive typing.h: keys hold a
hand back (2 s now, was 1), POINTER_PINCH_TYPING=0 is no guard, and a
chord's pinch by a hand on the keys is held back while one lifted off
them goes ahead. The chord's own pinch counts as a gesture held for
typing's "busy" (free if out of the keyboard zone when keys come), as
the left's hold does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:08:29 -06:00
DeeJanuzandClaude Opus 5.5 fae63a8820 Merge branch 'hands-reach' into hands-next: 30 cm touch, wrist turn, grab that
Conflict: docs/reference.md's hands settings line (typing's POINTER_TYPING_*
keys next to reach's HANDS_NEAR_REACH, HANDS_GRAB_ROTATE* and
HANDS_GRAB_THAT*): both kept.

Typing and grab that: a hand typing holds back (the helper's "typing" word)
doesn't arm or fire "grab that" either, so it never claims a fist as near;
vr.cpp's UpdateGrabThat leaves it out, as Poke and Grab already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:53:25 -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
DeeJanuzandClaude Opus 5.5 24d2d12397 pointer: the gaze chord in the helper, and hand methods as their own switches
The helper sends the chord's right button (btn b) apart from the left's
press, so both reach the panel; the chord ends with its pinch, its hand
lost, the pointer going, a controller on its side, or the calibration
panel. POINTER_GAZE_PINCH (1) turns clicks at the gaze off on their own.
HANDS_NEAR=on runs the handoff for ft-screens' direct touch without
POINTER_HANDS (HANDS_NEAR=1 keeps its meaning), and the relay feeds it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:46:31 -06:00
DeeJanuzandClaude Opus 5.5 e511c705f9 ft-screens: turn a carried panel with the wrist (HANDS_GRAB_ROTATE)
While one hand carries a panel, the hand's turn since the grab turns it
too, 1:1 about yaw, pitch and roll (user, 2026-10-10: line displays up).
The turn comes from the wrist and the index and little knuckles, which
hold still while the fingers pinch, smoothed with fh_smooth's Direction
filter, relative to facing the head across the floor from the hand, and
is applied on top of the panel facing the head: carrying a panel round
you still only keeps it facing you. Nothing turns until the hand has
turned HANDS_GRAB_ROTATE_DEADZONE (5 deg), then it turns from there
without a jump; within 2.5 deg of level it snaps level. Let go, it keeps
the turn, and the next grab starts from it. Corner and tab grabs only
resize; a stretch is unchanged and the hand left holding starts afresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:32:53 -06:00
DeeJanuzandClaude Opus 5.5 56fb3b3d82 pointer: typing holds hands back: no pinches, grips or ray, and ft-screens hears it
The helper feeds typing.h the relay's keys and each hands frame. A hand it
holds back makes no pinch, middle pinch or grip (ray, gaze or plain), and has
no ray: one that drove the pointer drops it (to the mouse or the gaze, not
the controllers) and can't claim it again until lifted off the keys. A hand
pointing or holding a gesture out of the keyboard zone isn't held back by
the other hand's keys. ft-screens gets "typing <L> <R>" on a change and every
second. The old 1 s guards (beginHold, gazehand's) give way to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:30:06 -06:00
DeeJanuzandClaude Opus 5.5 388e8584d4 ft-screens: a hand within 30 cm of a panel touches it (HANDS_NEAR_REACH)
The near claim reaches 0.30 m (was 0.15): within it the pointer helper
hides that hand's ray and leaves its pinches alone, and the index pokes
(user, 2026-10-10: "near = poke within a foot"). A hand near a panel
stays near until 5 cm farther out, then 250 ms on, so the ray doesn't
flicker at the zone's edge. A hand resting low by the body (the poke's
resting rule) is near only within 0.15 m, so a low panel within reach
doesn't take gaze pinches from the lap. The poke's rules are unchanged:
on the PR #3 desk and touch replays the presses are the same as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:25:39 -06: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 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
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 93adab7302 Hand ray: the torso faces the head and the hands (StereoKit's body model)
Ideas from StereoKit (MIT, 07b9f0d), credited where a formula comes from it:
- The torso faces the head's forward counted twice plus the direction to
  each wrist seen (POINTER_RAY_TORSO_WRISTS, on), so looking aside while
  pointing swings the shoulders, and the ray, less. probe-moves replay: the
  shoulder's share of a still hand's ray motion 20 -> 18%, p90 2.0-3.7 ->
  1.7-3.6 deg/s; the tap capture: nothing worse.
- The ray can turn toward the hand's own direction, from the palm's axes
  (wrist, middle, ring and little knuckles) tilted -30/8 deg
  (POINTER_RAY_HAND_WEIGHT, 0). Off: every weight measured worse (at 0.3:
  judder 0.07-0.14 -> 0.11-0.33 deg rms per 90 Hz tick, raw jumps over
  3 deg 159 -> 330). The gate still tests the shoulder's ray.
- The knuckle point can be pinned to the palm's axes during a pinch, easing
  back on the steady shoulder's schedule (POINTER_RAY_ANCHOR, 0). Off: our
  pinch point is the knuckles already, and pinned it shook as much or more
  and kicked no less on release, fitted or not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:15:51 -06:00
DeeJanuzandClaude Opus 5.5 2ae1a36b33 Hands: flag tracking failures (ft-handsctl flag, a rolling buffer in ft-hands)
ft-hands keeps the last 12 s of hand crops (each view's landmark crop, 1.6x, at most
224 px), its hands, gestures and landmarks in RAM (track/flagbuf.h, HANDS_FLAG_BUFFER=1).
`ft-handsctl flag [note]` asks for them through run_dir/flag; ft-hands writes them and the
next 2 s to HANDS_FLAG_DIR/flag-<time> from a low-priority thread, for frame-hands'
offline tools. ft-handsctl also saves a /dev/video99 frame and ft-handview's head poses,
and the drawn hands pulse cyan when a flag is taken (ft-handview's new flash and poses
commands). ft-handreplay --flag-at/--flag-every makes the same flags from recordings.
Measured on a replayed ring: +58 MB peak RSS, 0.1 ms a crop on an A720 (about 1% of a
core). Unit tests: tests/flagbuf_test.cpp (make check), tests/test_handsflag.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:11:53 -06:00
DeeJanuzandClaude Opus 5.5 920d16ee5e Hands: the drawn hands instead of the passthrough holes in the panels (HANDS_CUTOUTS=1 brings them back)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:00:31 -06:00
DeeJanuzandClaude Opus 5.5 f16ad59a92 ft-handview: a robot hand built from the skeleton (HANDS_VIEW_MODEL=robot)
A stylized robot hand made in code, no asset: rigid pieces, each fixed to
one bone, so nothing bends or stretches out of shape. A light shell per
finger bone (a tapered box with rounded edges and ends), dark balls at the
knuckles and the thumb's base, dark hinges at the other joints, a palm of a
dark core between a palm plate and a back plate with panel lines, and a
short closed cuff past the wrist. Cyan accents glow: fingertip pads, a line
across the knuckles, a dot in the palm, a ring round the cuff. solid is
ceramic white over dark grey metal; ghost is pale and see-through.

screens/handrobot.cpp builds one mesh (2373 vertices, 4360 triangles) once,
a right hand on DemoPose's open hand, in 16 segments' frames. The frames are
handskin's rule (new handskin::TrackedFrames); a left hand's are a right
one's mirrored, so the same mesh draws it mirrored. Each vertex is fixed to
its bone's start or end: shells keep their rounded ends, the straight middle
takes up the tracked length, fingertips end at the tracked tips. The vertex
shader applies one segment's matrix per vertex; no cap pass (it's closed).

Cost (--snapshot, frame-job 4 cores, same run): robot 0.21-0.24 ms CPU and
0.55-0.57 ms GPU wait per draw, against asset 0.28/0.66, babylon 0.26/0.66,
mrtk3 0.25/0.66.

Tests: segments start at their joints along their bones, no vertex past its
bone's length, fingertips at the tracked tips, left = right mirrored vertex
for vertex, rigid segments across poses, only the middle stretches with a
longer bone, accents on the right sides.

No free robot hand asset fit; screens/assets/hands/README.md says what was
looked at.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:51:50 -06:00
DeeJanuzandClaude Opus 5.5 366f5efa4c ft-handview: a cleaner look, a capped wrist, and four more hand models
The look: no more drawn outline. The old rim pass lit a hard line wherever the
hand met the background or a part of it further back, so a finger over the palm
got scratchy white lines. Now the body pass keeps only the surface nearest the
eye (depth) and writes how squarely it faces the eye, the fade, a light from
above and how near a fingertip; the second pass turns that into a soft fresnel
rim, a slight fingertip glow and an antialiased outline. ghost glows white
toward its edges; solid is lit from above with a soft cool rim.

The wrist: the model is cut open where the fade ends, so a hand pointing away
showed its inside (a hollow tube, a fist's fingertips through the palm). A
stencil pass counts front faces up and back faces down along each view (on its
own, with the depth test off: early depth would drop hidden ones); where a view
went in through the cut, a disc where the fade begins covers it.

The models: HANDS_VIEW_MODEL=asset (generic-hand, still the default) | babylon
(Babylon.js, CC BY 4.0) | mrtk3 (BSD-3-Clause, right only, mirrored) | godot
(Godot XR Tools, CC0) | mrtk2 (MIT) | capsules. Each is in
screens/assets/hands/<name>/ with its licence and a README giving the source
commit, hashes and conversion (the MRTK ones were FBX: assimp, then the new
screens/tools/hand-glb-clean.py). hand-asset.py maps each skeleton onto WebXR's
25 joints by name and writes them all into handasset.h; handskin takes the
model's index.

Cost (--snapshot, asset): CPU 0.34 ms per draw (was 0.32-0.33), GPU wait
0.61-0.62 ms per draw (was 0.57). --snapshot now prints the GPU wait.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:26:31 -06:00
DeeJanuzandClaude Opus 5.5 8a0176075e Merge branch 'hands-look' into hands-full
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:46:14 -06:00
DeeJanuzandClaude Opus 5.5 db2e633502 Merge branch 'hands-gaze' into hands-full
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:44:51 -06:00
DeeJanuzandClaude Opus 5.5 3a7a532cb9 ft-handview: your hands as a 3D hand model, with Room View on too
The rendered hands are now the WebXR Input Profiles' generic hand (MIT),
posed by the tracked skeleton and skinned on the GPU: per joint a frame
from the bone and the palm, scaled to the hand and each bone's length.
Two looks, HANDS_VIEW_STYLE=ghost|solid; HANDS_VIEW_MODEL=capsules keeps
the tube hand. With HANDS_VIEW_ROOMVIEW=1 (the default) the hands also
float over the cutout with Room View on. --snapshot draws demo poses
into a PNG without SteamVR, and the cost (0.4 ms a draw, the tubes 0.6).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:44:29 -06:00
DeeJanuzandClaude Opus 5.5 eafb73483b Near grabs: a carried panel moves smoothly and holds still in a still hand
A near grab carried the pinch point moved ahead along the hand's velocity, which is mostly
noise times the lead, so the panel shook. It now carries the pinch point as the cameras saw
it, smoothed (fh_smooth.h, read every frame ft-screens draws), and moves nothing until the
point is 1 cm from where the pinch began, then carries on from there without a jump. The
head the panel turns to face is smoothed too. On the probe-moves replay the carried point's
second difference per 90 Hz tick went from 13-17 mm rms to 2.3-2.6 mm; it now trails the
capture by 85 ms while moving.

Settings: HANDS_GRAB_SMOOTH_MINCUTOFF, HANDS_GRAB_SMOOTH_BETA, HANDS_GRAB_DEADZONE.
handinput_test --replay reports the grab input's smoothness.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:43:05 -06:00
DeeJanuzandClaude Opus 5.5 0d6b0c93b5 Hand ray: drags move smoothly and hold steady, and a quick tap stays a click
The ray's One Euro filters (new hands/include/fh_smooth.h) run in continuous time and the
helper reads the ray every loop, so the pose sent to ft_pointer moves at about 100 Hz
instead of jumping at the tracker's 15-30 Hz (the dashboard's dragger made those jumps
jerky). The direction is filtered on the sphere. On the probe-moves replay the pose's
second difference per 90 Hz tick went from 0.90-0.96 deg rms to 0.24-0.26 at the same lag.

While a ray pinch is held the ray pivots on the shoulder as it was at the press, so looking
around doesn't move what's dragged; the reach for push and pull is filtered. A pinch's
first 0.3 s takes 6 deg of turn before it drags. One failed read of the gestures file no
longer ends a drag, and the log says why a drag ended or the ray went off.

Settings: POINTER_RAY_SMOOTH_MINCUTOFF, POINTER_RAY_SMOOTH_BETA, POINTER_RAY_SMOOTH_HOLD,
POINTER_RAY_STEADY, POINTER_RAY_TAP, POINTER_RAY_TAP_DEADZONE. rayeval reads publish dumps
and reports the smoothness at the display's rate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:42:59 -06:00
DeeJanuzandClaude Opus 5.5 d348e0a260 Gaze + hands: look and pinch to click, pinch and move to nudge the pointer
With gaze mode and POINTER_HANDS on, the gaze is the pointer and the hand ray
stays off (POINTER_GAZE_HAND_RAY=1 brings it back). A pinch clicks where you
looked 75 ms before ft-hands saw it; held and moved past 4 deg of hand
movement (POINTER_GAZE_PINCH_DEADZONE) it nudges the pointer at 0.3x
(POINTER_GAZE_PINCH_GAIN), and the correction is a gaze lesson. Held still for
POINTER_GAZE_HOLD it's a press the hand drags. A middle pinch right-clicks at
the gaze. One log line per pinch. Rules in pointer/helper/gazehand.h, tested
in pointer/tests/gazehand-test.cpp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:39:05 -06:00
DeeJanuzandClaude Opus 5.5 3c4964bc56 Hand cutouts: a round hole per hand by default (HANDS_MATTE=circle)
Until the tracking is good enough for a hand-shaped hole, each hand cuts a
disk facing the eye, wide enough for the whole hand plus the margin, drawn
as the stadium around the ellipse each eye sees on the screen with a soft
edge. mesh and capsules stay selectable (HANDS_MATTE, cutouts matte).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:17:04 -06:00
DeeJanuzandClaude Opus 5.5 2c57eef75c Merge branch 'hands-settings' into hands-full
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:13:28 -06:00
DeeJanuzandClaude Opus 5.5 64bead79c9 Merge branch 'hands-palm-menu' into hands-full
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:13:16 -06:00
DeeJanuzandClaude Opus 5.5 07ce0d9d59 Hand ray: the laser stays with one hand, and a pinch clicks where it pointed
Live test 2026-10-09: taps took several tries and the laser jumped between
hands. The owner's ray dropping for a moment (closing the fingers turns the
hand) handed the laser to the other raised hand at once, for good; a pinch
by the other hand pressed where the old owner's ray pointed; a pinch whose
ray had just dropped became a press at a stale spot, or only woke the
pointer.

- The owner keeps the laser through POINTER_RAY_GRACE (0.5 s) of its ray
  being down; the other hand takes it by pinching, by raising its ray while
  the owner's is down, or after the grace (pointer/helper/rayowner.h).
- A pinch on a ray that dropped within the grace, or is on its way up,
  brings it back (fh_ray.h ShowForPinch) and is a ray pinch.
- Aim lock: the press goes along the ray from just before the fingers
  began to close, after a pose along it has gone out; a tap that ends
  before its press clicks there too.
- Each ray follows one tracked hand through a brief flip of ft-hands'
  left/right call.
- Log: owner changes with the reason, the owner's ray down and back, and
  one line per pinch saying what it clicked or why not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:12:46 -06:00
DeeJanuzandClaude Opus 5.5 98475f2b7d Hands: HANDS_MODE runs hand tracking with Frametop, with SteamVR, or not at all
hands/handsmode.py starts and stops ft-camd, ft-hands and the hand view
(frametop-handview.service, when installed) by HANDS_MODE: frametop =
while the desktop runs (session hooks), always = wanted by
steamvr.service (and kept running through game pauses), off = the
default, only ft-handsctl on as before. ft-handsctl mode sets it;
missing ft-camd capabilities start nothing and say what to run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:12: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