mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 00:00:07 +02:00
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>
This commit is contained in:
1 parent
1f46616c75
commit
964c250858
3 files changed
+53
-19
No files matched your search
+1
-1
@@ -91,7 +91,7 @@ A few overlays need special handling:
|
||||
|
||||
Head follow is experimental and off by default. It works, but it's only lightly tested, and the feel is mostly a matter of its settings; polishing it is left open. With it on (`POINTER_FOLLOW=1`, or a mouse button mapped to Head follow on/off), the cursor rides on a reference direction, where you were facing when your head last settled, and keeps its offset from it. The mouse can put the cursor anywhere up to `POINTER_FOLLOW_REACH` (70 degrees) from the reference, a corner of your view included. While your head stays within `POINTER_LEASH_DEG` of the reference, nothing moves on its own. Once your head has been past the leash for `POINTER_LEASH_DELAY` (0.2 s, so a glance out and back doesn't count), the reference eases to where you're facing (time constant `POINTER_LEASH_RETURN`, 0.2 s), never falling further behind than the leash, and the cursor ends up back where it was in your view. Then it waits for the leash again. Two earlier versions didn't work out. Moving the reference only while your head pulled at the end of the leash left it up to the leash off after you turned back, and getting it centred again meant overshooting with your head. Easing it toward your facing all the time moved the cursor on every small head movement. A leash of 0 makes the reference your facing direction, so the cursor is locked to your view, and mouse movement shifts it within the view. Head roll is ignored, so tilting your head doesn't swing the cursor around. While the left button is held the cursor stays put in the room, so your head can't nudge a click or a drag. When you let go, it carries on from where it is instead of jumping.
|
||||
|
||||
Gaze mode is experimental and off by default (`POINTER_GAZE=1`, the Gaze page of Frametop Input Settings, `gaze/ft-gazectl on`, or a mouse button or key combination mapped to Gaze pointer on/off). It's MAGIC pointing (Zhai, Morimoto and Ihde, 1999): the pointer goes where you look, and the mouse does the last bit. The gaze service (`gaze/ft-gazed`) sends the helper the corrected gaze at 90 Hz (from one eye while the tracker has lost the other), and while the gaze has the pointer, the cursor ray is that gaze from the eye. The pointer is aimed at the gaze each frame, not steered toward it, so nothing can pile up. An earlier try in the gaze probe steered the pointer with relative moves, and lost it when the pointer went idle or a controller had the laser. By default (`POINTER_GAZE_MOUSE_MOVE=held`, the Gaze page's Mouse movement switch) moving the mouse does nothing while the gaze has the pointer: 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 the lessons aren't polluted by mouse moves to somewhere else (they used to be kept out by an 8 degree limit, which also dropped real corrections when the tracker was further off). With the gaze stale for a second, in a game, or with the headset off, the mouse moves the pointer as usual; with `free`, moving the mouse takes the pointer from the gaze. A left press while the gaze has the pointer isn't sent at once: the pointer stops where the gaze put it, you drag it onto what you meant with the button still down (panels only see it hover), and the release clicks there. Clicking at once clicked wherever the gaze was, often the wrong thing, before you could correct it. The drag is the correction. Snapping the pointer onto buttons and links is deferred: it needs accessibility (AT-SPI) on in the Frametop session, where it's off (no registry runs), plus app restarts, and it makes Chromium and Electron apps use more CPU. A press held still for `POINTER_GAZE_HOLD` (0.5 s) becomes a real press, so drags still work: hold, then move. The right button works the same way, with the right click on the release, and pressing it while the left press is held back starts a drag where the pointer is, like Meta+J then Meta+K. Outside games the pointer then stays: the mouse going idle doesn't release it. A moving controller still releases it, as without gaze. Gaze mode is a mouse and keyboard feature: Steam reads the Frame controllers itself, outside SteamVR's bindings, so controller clicks at the gaze kept knocking SteamVR out of laser mode (see `docs/gaze-controllers.md`). Keyboard clicks (Meta+J, Meta+K) hold the dot still in your view while the keys are down, so the head, not the mouse, does the last bit; a quick tap clicks where the dot was at the press, since the head moves as you hit the keys. The relay hides Meta from the desktop as soon as such a combination fires, because KWin takes Meta with a mouse button as a window move or resize, which swallowed the clicks. The dot shows all the time by default. With `POINTER_GAZE_DOT=moving` it shows only while the mouse moves it (`POINTER_GAZE_SHOW`), while a press is held, and as a pulse for each click; otherwise it's transparent, so the laser still lands on it. Looking more than `POINTER_GAZE_RETAKE` (5 degrees) away from it, with the mouse still, gives it back, so small eye movements around the pointer don't pull it off what you're doing. A mouse nudge before a click whose correction is within `POINTER_GAZE_NUDGE_MAX` (55 degrees, half of what the headset shows across) is sent to the gaze service as a lesson: you were looking at where you clicked when the mouse took over, so the nudge is the eye tracker's error there. Using it is what calibrates it. A one-dot check in a panel fixed to the headset tops that up when the headset goes on, when our tracker thinks it moved, and when a correction is past that limit (the tracker is far off, so a click there isn't trusted as a lesson), and the full calibration runs in the same panel. The limit was 8 degrees, which dropped every correction while our tracker was 12 off. Its dots sit at known directions from the headset, so the panel needs no screen geometry, and each dot takes the gaze when it has held still rather than at a press: what the tracker says doesn't have to be close for the capture to work. See `gaze/README.md` for the service, the calibration, and what was measured.
|
||||
Gaze mode is experimental and off by default (`POINTER_GAZE=1`, the Gaze page of Frametop Input Settings, `gaze/ft-gazectl on`, or a mouse button or key combination mapped to Gaze pointer on/off). It's MAGIC pointing (Zhai, Morimoto and Ihde, 1999): the pointer goes where you look, and the mouse does the last bit. The gaze service (`gaze/ft-gazed`) sends the helper the corrected gaze at 90 Hz (from one eye while the tracker has lost the other), and while the gaze has the pointer, the cursor ray is that gaze from the eye. The pointer is aimed at the gaze each frame, not steered toward it, so nothing can pile up. An earlier try in the gaze probe steered the pointer with relative moves, and lost it when the pointer went idle or a controller had the laser. By default (`POINTER_GAZE_MOUSE_MOVE=held`, the Gaze page's Mouse movement switch) moving the mouse does nothing while the gaze has the pointer: 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 the lessons aren't polluted by mouse moves to somewhere else (they used to be kept out by an 8 degree limit, which also dropped real corrections when the tracker was further off). With the gaze stale for a second, in a game, or with the headset off, the mouse moves the pointer as usual; with `free`, moving the mouse takes the pointer from the gaze. A left press while the gaze has the pointer isn't sent at once: the pointer stops where the gaze put it, you drag it onto what you meant with the button still down (panels only see it hover), and the release clicks there. Clicking at once clicked wherever the gaze was, often the wrong thing, before you could correct it. The drag is the correction. Snapping the pointer onto buttons and links is deferred: it needs accessibility (AT-SPI) on in the Frametop session, where it's off (no registry runs), plus app restarts, and it makes Chromium and Electron apps use more CPU. A press held still for `POINTER_GAZE_HOLD` (0.5 s) becomes a real press, so drags still work: hold, then move. The right button works the same way, with the right click on the release, and pressing it while the left press is held back starts a drag where the pointer is, like Meta+J then Meta+K. That drag lasts while either button (or key) is held, so a second right press, or a second Meta+K, is free to pan and tilt the panel being dragged; with the keyboard, the head turns it. Outside games the pointer then stays: the mouse going idle doesn't release it. A moving controller still releases it, as without gaze. Gaze mode is a mouse and keyboard feature: Steam reads the Frame controllers itself, outside SteamVR's bindings, so controller clicks at the gaze kept knocking SteamVR out of laser mode (see `docs/gaze-controllers.md`). Keyboard clicks (Meta+J, Meta+K) hold the dot still in your view while the keys are down, so the head, not the mouse, does the last bit; a quick tap clicks where the dot was at the press, since the head moves as you hit the keys. The relay hides Meta from the desktop as soon as such a combination fires, because KWin takes Meta with a mouse button as a window move or resize, which swallowed the clicks. The dot shows all the time by default. With `POINTER_GAZE_DOT=moving` it shows only while the mouse moves it (`POINTER_GAZE_SHOW`), while a press is held, and as a pulse for each click; otherwise it's transparent, so the laser still lands on it. Looking more than `POINTER_GAZE_RETAKE` (5 degrees) away from it, with the mouse still, gives it back, so small eye movements around the pointer don't pull it off what you're doing. A mouse nudge before a click whose correction is within `POINTER_GAZE_NUDGE_MAX` (55 degrees, half of what the headset shows across) is sent to the gaze service as a lesson: you were looking at where you clicked when the mouse took over, so the nudge is the eye tracker's error there. Using it is what calibrates it. A one-dot check in a panel fixed to the headset tops that up when the headset goes on, when our tracker thinks it moved, and when a correction is past that limit (the tracker is far off, so a click there isn't trusted as a lesson), and the full calibration runs in the same panel. The limit was 8 degrees, which dropped every correction while our tracker was 12 off. Its dots sit at known directions from the headset, so the panel needs no screen geometry, and each dot takes the gaze when it has held still rather than at a press: what the tracker says doesn't have to be close for the capture to work. See `gaze/README.md` for the service, the calibration, and what was measured.
|
||||
|
||||
Replacing a loaded driver's files, as re-running the installer used to do, leaves SteamVR honoring the virtual controller's hand role but not its laser claim: the dashboard pointer stays unassigned until SteamVR restarts. The driver installer now leaves an unchanged driver in place.
|
||||
|
||||
|
||||
+2
-2
@@ -28,8 +28,8 @@ Gaze as an input method for the whole desktop, without replacing anything of Ste
|
||||
|
||||
Looks down past the screens (under 20 degrees down, on no Frametop screen: a glance at the keyboard) aren't sent, so the pointer stays where it was instead of following you down, and eyes lost there aren't counted. It drops blinks (both eyes closing or lost), smooths with a fixation lock, and sends the result to the pointer helper 90 times a second. It follows SteamVR's eye tracking log, and when the headset goes back on (SteamVR starts its eye model over, and the error moves), older lessons count less, so the first few after relearn the offset.
|
||||
- The pointer helper's **gaze mode** (off by default: the Gaze page of Frametop Input Settings, `gaze/ft-gazectl on`, `POINTER_GAZE=1` in `~/.config/frametop.conf`, or a mouse button or key combination mapped to "Gaze pointer on/off") works like MAGIC pointing (Zhai et al., 1999). The pointer goes where you look. Move the mouse and it's the mouse's, from where the gaze put it, for the last bit. Look well away (5 degrees) and the gaze takes it back. A press isn't sent at once: the pointer stops where the gaze put it, and if that's wrong, drag it onto what you meant with the button still held; the click happens where you let go. To drag something, hold the press still for half a second first (`POINTER_GAZE_HOLD`), then move. Outside games the pointer stays on while gaze mode is on, until a controller is picked up. The dot shows all the time (`POINTER_GAZE_DOT=moving`: only while the mouse moves it, while a press is held, and as a pulse when you click). Gaze mode works with the mouse and the keyboard, not the controllers ([docs/gaze-controllers.md](../docs/gaze-controllers.md) explains why).
|
||||
- **The mouse only corrects** (the default; the Gaze page's Mouse movement switch, `POINTER_GAZE_MOUSE_MOVE=held`): while the gaze has the pointer, moving the mouse does nothing. The buttons work like Meta+J and Meta+K: press and hold one and the pointer stops where you look; move the mouse onto what you meant and let go to click there (a left or a right click). Held still for half a second, a press is a real one (to drag). Once you've moved, the left button alone only clicks: press the right one while still holding the left to start a drag there, and let go of either to drop it. A bumped or drifting mouse can't pull the pointer away, and every mouse move is a correction, so the tracker only learns from real ones. With the gaze stale for a second (the tracker stopped, eyes lost), in a game, or with the headset off, the mouse moves the pointer as usual. `free` lets the mouse take the pointer any time, as before.
|
||||
- **Keyboard clicks** (Meta+J left, Meta+K right; other key combinations on the Keyboard page of Input Settings): tap to click where you look. A quick tap (let go within 0.25 s, `POINTER_KEY_TAP`) clicks where the dot was when you pressed, whatever your head did, and tells the gaze service it was right there. Hold instead, and the dot stays put in your view: turn your head until it sits on what you meant, and let go to click there (the correction is a lesson, as with the mouse, but up to 30 degrees whatever `POINTER_GAZE_NUDGE_MAX` says: a keyboard correction is always meant). Hold still for half a second to press for real, then turn your head to drag. With Meta+J held, Meta+K presses where the dot is now, so you can correct first and then drag; let go of either to drop.
|
||||
- **The mouse only corrects** (the default; the Gaze page's Mouse movement switch, `POINTER_GAZE_MOUSE_MOVE=held`): while the gaze has the pointer, moving the mouse does nothing. The buttons work like Meta+J and Meta+K: press and hold one and the pointer stops where you look; move the mouse onto what you meant and let go to click there (a left or a right click). Held still for half a second, a press is a real one (to drag). Once you've moved, the left button alone only clicks: press the right one while still holding the left to start a drag there; it lasts while either button is held. Press the right one again (a double right click, the left still held) to pan and tilt what you're dragging, as a right press does during any drag. A bumped or drifting mouse can't pull the pointer away, and every mouse move is a correction, so the tracker only learns from real ones. With the gaze stale for a second (the tracker stopped, eyes lost), in a game, or with the headset off, the mouse moves the pointer as usual. `free` lets the mouse take the pointer any time, as before.
|
||||
- **Keyboard clicks** (Meta+J left, Meta+K right; other key combinations on the Keyboard page of Input Settings): tap to click where you look. A quick tap (let go within 0.25 s, `POINTER_KEY_TAP`) clicks where the dot was when you pressed, whatever your head did, and tells the gaze service it was right there. Hold instead, and the dot stays put in your view: turn your head until it sits on what you meant, and let go to click there (the correction is a lesson, as with the mouse, but up to 30 degrees whatever `POINTER_GAZE_NUDGE_MAX` says: a keyboard correction is always meant). Hold still for half a second to press for real, then turn your head to drag. With Meta+J held, Meta+K presses where the dot is now, so you can correct first and then drag; the drag lasts while either key is held. Meta+K during a Meta+J drag (again, after starting it with Meta+K: a double Meta+K) pans and tilts what you're dragging while it's held: turn your head to turn it.
|
||||
- **Learning from nudges:** if the mouse took the pointer from the gaze and moved it (0.2 degrees or more, and the correction within `POINTER_GAZE_NUDGE_MAX`, 55 degrees: half of the 109 the headset shows across) before you clicked, or you dragged a held press that far, you were nudging it onto what you looked at. The helper sends that as a lesson, from the raw gaze when the mouse took over to where you clicked, and ft-gazed learns it. So using it is what calibrates it. The raw gaze is one ft-gazed sent, so it also finds when that look was, and what each eye read then. With SteamVR, each eye learns its own error. With our tracker, the look goes to it as a click, like the probe's, and it relearns how the headset sits on your face. After the headset was off, your first nudge and click there resets that (the probe's one-dot check does the same). A correction past `POINTER_GAZE_NUDGE_MAX` isn't learned: the helper asks ft-gazed for the quick check instead ("recheck", after its 2-minute cooldown). Tested on our tracker's 409 clicks since its Sep 29 calibration: a one-dot check set from any one of them put the next 2 minutes' clicks within 15 degrees (99% within 4.2) and the next 10 minutes' within 25 (the far ones after the headset moved), so the check gets back well under it. The limit used to be 8 degrees, and live on 2026-10-01 our tracker was 12 off after the headset went on, so every correction was dropped. One lesson moves the whole correction by only a third of what it measured (more near where it was taken), since in the first live test one 6 degree lesson moved everything and put the next target 7 degrees off. `ft-gazectl status` shows the lessons, and `ft-gazectl forget` drops them.
|
||||
- **Checks and calibration in the headset** (`gaze/gazecheck.py`, shown by `gaze/panel/ft-gazepanel`, a panel fixed to the headset that ft-gazed runs): a one-dot quick check opens when you put the headset on (SteamVR's tracker sees your eyes for 3 s after none for 3 s; its "HMD on" log line can't say, since it repeats every minute or so and can stay on for hours with nobody in the headset), when our tracker asks for a click (its "reseat", when the headset may sit differently), at most once every 2 minutes, and from Quick check on the Gaze page. Look at the dot: it takes your gaze once it has held still for 0.6 s (the steadiness counts, not where the tracker puts it, so it works however far off it is), or at once with a left click or Meta+J; a right click or Meta+K closes it, and ignoring it changes nothing. It also runs when a click's correction was past `POINTER_GAZE_NUDGE_MAX`. The dot is still and the ring fills in quarters, so the panel is drawn again only a few times per dot. If the first 3 lessons after it are still over 2 degrees off, five dots follow. The full calibration (Calibrate on the Gaze page, or by itself when gaze mode comes on without one) is the probe's: three rounds, dark, medium and bright, of the middle and a ring around it, in a panel 64 degrees wide, with Frametop's screens hidden. Quitting it while there's still no calibration turns gaze mode off; turning it on again reopens it. For our tracker a check is a click and the calibration is its own (calib-point per dot); for SteamVR's, a check is a lesson for each eye and the calibration replaces calibration.json, and the lessons start over. Checks go to `checks.jsonl`.
|
||||
- Nothing writes to SteamVR, its eye tracker, or its files: ft-gaze maps the eye tracker's shared memory read-only. With no fresh gaze (a blink, the service stopped, the headset off), the pointer stays where it is, and the mouse works as always.
|
||||
|
||||
@@ -183,9 +183,11 @@
|
||||
// mouse's buttons work like the keyboard clicks: the right button's press is held back the same
|
||||
// way, and the right click comes on the release, where the pointer is by then (gaze_right).
|
||||
// Pressing the right button while the left one's press is held back presses the left button
|
||||
// where the pointer is now, and the mouse drags (gaze_left then gaze_right): letting go of
|
||||
// either drops it. So once you've moved the pointer, the left button alone only clicks; to drag
|
||||
// from there, press the right one. Held still for POINTER_GAZE_HOLD, either press is a real one.
|
||||
// where the pointer is now, and the mouse drags (gaze_left then gaze_right): the drag lasts
|
||||
// while either button is held. So once you've moved the pointer, the left button alone only
|
||||
// clicks; to drag from there, press the right one. Pressing the right one again (a double right
|
||||
// click, with the left still held) tilts, as a right press does during any drag (see Tilt).
|
||||
// Held still for POINTER_GAZE_HOLD, either press is a real one.
|
||||
// POINTER_GAZE_MOUSE_MOVE: held (the default) or free. Held: while the gaze has the pointer,
|
||||
// moving the mouse does nothing; it moves the pointer only during a press (as a correction,
|
||||
// like a keyboard click's head). So a bumped or drifting mouse can't pull the pointer off what
|
||||
@@ -203,7 +205,10 @@
|
||||
// whatever the head did meanwhile, and tells the gaze tracker it was right there (a lesson
|
||||
// with no correction). Pressing gaze_right while gaze_left aims (Meta+K with Meta+J held)
|
||||
// presses the left button where the dot is now, so you can correct first and then drag; the
|
||||
// first key let go drops it. Without gaze mode they work from wherever the pointer is.
|
||||
// drag lasts while either key is held. gaze_right pressed during a gaze_left drag (held still
|
||||
// into one, or again after starting one with it: a double Meta+K) tilts while it's held (see
|
||||
// Tilt): turning the head turns the panel, and the mouse can too; let go of it and the head
|
||||
// drags again from there. Without gaze mode they work from wherever the pointer is.
|
||||
// The gaze calibration panel (gaze/panel/ft-gazepanel, run by the gaze service): while
|
||||
// ft-gazed says it's up ("calpanel 1", renewed every second; it lapses 3 s after the last),
|
||||
// the dot hides and a press answers the panel instead of clicking: a left click or gaze_left
|
||||
@@ -832,7 +837,11 @@ int main() {
|
||||
// aimRight, the held-back press is the right button's; chordDrag, a drag the right button
|
||||
// began during the left's held-back press (the first release drops it, the other's is nothing).
|
||||
bool leftDown = false, rightDown = false, aimRight = false, chordDrag = false;
|
||||
bool ignoreLeftUp = false, ignoreRightUp = false;
|
||||
bool ignoreLeftUp = false;
|
||||
// The keyboard clicks' keys as the relay last said, and a tilt gaze_right holds during a
|
||||
// keyboard drag (see the top): the head turns the panel, from where it was (keyTiltYaw, keyTiltPitch).
|
||||
bool keyLeftDown = false, keyRightDown = false, keyTilting = false;
|
||||
double keyTiltYaw = 0, keyTiltPitch = 0;
|
||||
double tiltYaw = 0, tiltPitch = 0;
|
||||
double dragDistance = 0, lastDistance = 1.5; // drag lock: distance from the anchor at the press
|
||||
bool onVrSettings = false; // the cursor is on the SteamVR Settings page (kept while dragging)
|
||||
@@ -997,11 +1006,10 @@ int main() {
|
||||
nudgeAt = aimSince = Clock::now(), nudgeMoved = 0;
|
||||
aimHeld = true, aimRight = right;
|
||||
};
|
||||
// A drag the right button began (chordDrag): the first release drops it.
|
||||
auto chordDrop = [&](bool otherDown, bool &ignoreOtherUp) {
|
||||
// A drag the right button began (chordDrag): it lasts while either button is held.
|
||||
auto chordDrop = [&] {
|
||||
chordDrag = false;
|
||||
if (leftHeld) releaseLeft();
|
||||
ignoreOtherUp = otherDown;
|
||||
if (debug) std::printf("mouse drag dropped\n");
|
||||
if (debug) std::fflush(stdout);
|
||||
};
|
||||
@@ -1023,7 +1031,10 @@ int main() {
|
||||
ignoreLeftUp = false;
|
||||
return;
|
||||
}
|
||||
if (chordDrag) return chordDrop(rightDown, ignoreRightUp);
|
||||
if (chordDrag) {
|
||||
if (!rightDown) chordDrop(); // otherwise the right holds it (tilting, maybe)
|
||||
return;
|
||||
}
|
||||
if (aimHeld && !aimHand && !aimRight) {
|
||||
aimHeld = false;
|
||||
clickPress = true; // after this frame's pose, so it lands where the pointer was moved to
|
||||
@@ -1052,12 +1063,10 @@ int main() {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (ignoreRightUp) {
|
||||
ignoreRightUp = false;
|
||||
return true;
|
||||
}
|
||||
if (chordDrag) {
|
||||
chordDrop(leftDown, ignoreLeftUp);
|
||||
if (leftDown) return !swallowedRight; // the left holds it; a tilt's release ends the tilt
|
||||
tilting = swallowedRight = false;
|
||||
chordDrop();
|
||||
return true;
|
||||
}
|
||||
if (aimHeld && !aimHand && aimRight) {
|
||||
@@ -1744,8 +1753,17 @@ int main() {
|
||||
return true;
|
||||
};
|
||||
for (const KeyPress &k : keyPresses) {
|
||||
(k.right ? keyRightDown : keyLeftDown) = k.down;
|
||||
if (!k.down) {
|
||||
if (hold.src == Src::Head && (hold.right == k.right || hold.keyDrag)) {
|
||||
if (keyTilting && k.right) { // the tilt ends; the head drags again from here
|
||||
keyTilting = tilting = false;
|
||||
hold.engaged = false;
|
||||
if (debug) std::printf("hold key left: tilt ended\n");
|
||||
if (debug) std::fflush(stdout);
|
||||
}
|
||||
// The keys holding it: a gaze_left then gaze_right drag, either; otherwise its own.
|
||||
const bool held = hold.keyDrag ? keyLeftDown || keyRightDown : hold.right ? keyRightDown : keyLeftDown;
|
||||
if (hold.src == Src::Head && !held) {
|
||||
if (!hold.pressed && aimHeld && tnow - aimSince < std::chrono::duration<double>(keyTap)) {
|
||||
// A quick tap: a click where the dot was at the press, and the gaze was right.
|
||||
yaw = hold.pressYaw, pitch = hold.pressPitch;
|
||||
@@ -1753,9 +1771,18 @@ int main() {
|
||||
confirmLesson = true;
|
||||
}
|
||||
endHold(false);
|
||||
keyTilting = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (k.right && hold.src == Src::Head && !hold.right && hold.pressed && leftHeld) {
|
||||
// gaze_right during a gaze_left drag: tilt while it's held (see the top).
|
||||
tilting = tiltStart = keyTilting = true;
|
||||
headAngles(keyTiltYaw, keyTiltPitch);
|
||||
if (debug) std::printf("hold key left: tilt began\n");
|
||||
if (debug) std::fflush(stdout);
|
||||
continue;
|
||||
}
|
||||
if (k.right && hold.src == Src::Head && !hold.right && !hold.pressed && aimHeld) {
|
||||
// gaze_right while gaze_left aims: press the left button where the dot is now
|
||||
// (the correction is a lesson), then the head drags.
|
||||
@@ -1787,7 +1814,14 @@ int main() {
|
||||
}
|
||||
if (hold.src == Src::Head) {
|
||||
double hy, hp;
|
||||
if (headAngles(hy, hp)) steer(hy, hp, hold.pressed ? 0.3 : headDeadzone, 1.0);
|
||||
if (headAngles(hy, hp) && keyTilting) {
|
||||
// The head turns the panel, as the mouse does in a tilt; the pointer stays put.
|
||||
tiltYaw += std::remainder(hy - keyTiltYaw, 360.0);
|
||||
tiltPitch = std::clamp(tiltPitch + hp - keyTiltPitch, -80.0, 80.0);
|
||||
keyTiltYaw = hy, keyTiltPitch = hp;
|
||||
} else if (headAngles(hy, hp)) {
|
||||
steer(hy, hp, hold.pressed ? 0.3 : headDeadzone, 1.0);
|
||||
}
|
||||
}
|
||||
fh_gestures_t hg;
|
||||
const bool handOk = handsOn && handFile.Read(hg);
|
||||
|
||||
Reference in new issue
Block a user