mirror of
https://github.com/lhns/steam-frame-nix.git
synced 2026-10-06 05:00:24 +02:00
With both controllers on the keyboard, the idle one's hover (mouse events,
pointerId 1) arrived during the other's press and went into the swipe path
("tust" for "test"). A gesture now belongs to the contact that started it
(touch identifier, pointerId or mouse; gesture-input.js): other contacts'
moves, releases and cancels are ignored, a second press meanwhile stays
Steam's tap. Test with a recorded two-controller swipe.
77 lines
3.2 KiB
JavaScript
77 lines
3.2 KiB
JavaScript
// gesture-input.js: which input events belong to the gesture in progress
|
|
// (patch.js). Evaluates to { VERSION, create }.
|
|
//
|
|
// SteamVR's lasers reach the keyboard page as touches (a trigger press: one
|
|
// touch with a new identifier per press) and as mouse hover (pointerId 1,
|
|
// no hand distinguisher; Chromium's pointer events for touches are cancelled
|
|
// after a few moves). With both controllers on the keyboard the idle one's
|
|
// hover keeps arriving during the other's press. A gesture is therefore owned
|
|
// by the contact that started it: a touch identifier, a pointerId or the
|
|
// mouse. Moves, the release and a cancel of other contacts are ignored, so
|
|
// they never enter the swipe path or end it. A second press while a gesture
|
|
// runs is ignored (Steam handles it as stock); one whose owner is gone
|
|
// (missed release) replaces it.
|
|
(() => {
|
|
const VERSION = 1;
|
|
|
|
function create() {
|
|
let owner = null, sawPointer = false;
|
|
|
|
// The contacts of an event: [{ id, x, y, target }].
|
|
function contacts(e) {
|
|
if (e.type.startsWith('touch')) {
|
|
const list = e.type === 'touchmove' ? e.touches : e.changedTouches;
|
|
return [...(list || [])].map((t) => ({ id: `t${t.identifier}`, x: t.clientX, y: t.clientY, target: t.target }));
|
|
}
|
|
if (e.type.startsWith('pointer')) {
|
|
sawPointer = true;
|
|
if (e.pointerType === 'touch') return []; // touch events carry these
|
|
return [{ id: `p${e.pointerId}`, x: e.clientX, y: e.clientY, target: e.target }];
|
|
}
|
|
if (sawPointer) return []; // pointer events carry the mouse
|
|
return [{ id: 'm', x: e.clientX, y: e.clientY, target: e.target }];
|
|
}
|
|
const PHASE = {
|
|
touchstart: 'down', pointerdown: 'down', mousedown: 'down',
|
|
touchmove: 'move', pointermove: 'move', mousemove: 'move',
|
|
touchend: 'up', pointerup: 'up', mouseup: 'up',
|
|
touchcancel: 'cancel', pointercancel: 'cancel',
|
|
};
|
|
|
|
return {
|
|
VERSION,
|
|
get owner() { return owner; },
|
|
// { phase, id, x, y, target } if the event concerns the gesture (or may
|
|
// start one: phase 'down'), else null. An 'up' or 'cancel' ends the
|
|
// ownership.
|
|
read(e) {
|
|
const phase = PHASE[e.type];
|
|
if (!phase) return null;
|
|
const cs = contacts(e);
|
|
if (phase === 'down') {
|
|
// One touch at a time starts a gesture (a multi-touch press is Steam's).
|
|
if (e.type === 'touchstart' && (e.touches?.length ?? 0) > 1) {
|
|
if (owner?.[0] === 't' && ![...e.touches].some((t) => `t${t.identifier}` === owner)) owner = null;
|
|
return null;
|
|
}
|
|
const c = cs[0];
|
|
if (!c) return null;
|
|
// Busy: ignored, unless the owner is gone (a lone touch, or its own id again).
|
|
if (owner && !(e.type === 'touchstart' || c.id === owner)) return null;
|
|
owner = null;
|
|
return { phase, ...c };
|
|
}
|
|
if (!owner) return null;
|
|
const c = cs.find((x) => x.id === owner);
|
|
if (!c) return null;
|
|
if (phase !== 'move') owner = null;
|
|
return { phase, ...c };
|
|
},
|
|
claim(c) { owner = c.id; }, // the 'down' contact starts a gesture
|
|
release() { owner = null; },
|
|
};
|
|
}
|
|
|
|
return { VERSION, create };
|
|
})()
|