A gesture-language interpreter for laptop touchpads. Raw touches come in via evdev straight from /dev/input/eventN (or a mouse fallback), get classified into gesture tokens by a CLOS pipeline, and feed a rule engine where sequences of gestures form a language
The formulas for the whole pipeline is written in docs/gesture-math.md; the spec which src/stroke.lisp implements
(asdf:load-system :touchtoy)
;; the demo: a real-time sigil-dueling card game (see below)
(touchtoy:start-touchpad-game) ; on a touchpad
(touchtoy:start-game) ; mouse fallback (no touchpad needed)
;; tools
(touchtoy:start-touchpad-monitor) ; terminal gesture printer + trail
(touchtoy:start-touchpad-canvas) ; swap-on-scratch demo + water ripples
(touchtoy:start-touchpad-monitor :grab t) ; exclusive grab
(touchtoy:start-monitor) ; mouse fallback monitor
(touchtoy:start-canvas)ESC quits, R restarts the game. Touchpad access needs membership in the
input group for /dev/input/eventN.
You vs the Jester. Your spellbook is a row of cards along the bottom; each card is a sigil, a sequence of gestures. Draw a card's sigil to cast it.
| sigil (draw this) | card | effect |
|---|---|---|
| line | Jab | quick damage |
| corner (quarter-arc) | Arc | damage |
| circle | Ward | shield |
| half-scratch (the arrow) | Pierce | damage, ignores shield |
| scratch | Reverse | reflect the Jester's telegraphed spell |
| corner -> line | Nova | big damage (a two-gesture combo) |
- Energy regenerates in real time; the Jester casts on its own clock, act live.
- Two fingers spread = Amplify the next cast; pinch = Surge energy; two-finger swipe scrolls the book (these stream live, not on lift).
- Hold a finger in the bottom-left corner = Guard (the spec's reserved global zone) — halves the next hit.
- Single sigils fire instantly; a combo (e.g. Nova) chains if you draw the next gesture within ~0.5 s, otherwise the partial incantation resolves on its own.
It is the framework's stress test: gesture language (sequenced sigils through the rule engine), speed (real-time energy + AI), liveness (streamed scroll/pinch), zones + modifier, and multitouch.
CLOS classes: dot, line, corner, circle, scratch, half-scratch,
expand, shrink, pan, modifier. Protocol: gesture-kind,
gesture-direction, gesture-from, gesture-to, gesture-bbox, gesture-inner.
Highlights of the corrected classifier (full detail in the math doc):
- corner = a quarter-circle arc (per the spec), not a straight diagonal line.
- scratch vs half-scratch is decided by the opening angle between the two arms, length-invariant. A scratch retraces; a half-scratch (the arrow / Aries ♈) mirrors.
- one stroke can contain several tokens (vertices split it).
- expand / shrink are isolated from translation and rotation by a similarity decomposition, so a multitouch swipe (
pan) or orbit is never misread as a pinch. - dot vs modifier is decided by duration + concurrency: a brief stationary tap is a
dot; a held one (or one held while another finger draws) is amodifiercarrying its zone.
Beyond the discrete on-lift tokens, the input layer streams a live channel while fingers are down: (:drag ...) for single-finger moves and (:scroll ...) / (:pinch …) for two fingers, so things can be dragged and scrolled in real time. A stroke the app consumes live is suppressed from the discrete stream.
(defrule name context pattern lambda-list &body body)Pattern items: :keyword (exact kind), (var keyword) (typed bind), var (bind any). Rules are tried longest-pattern-first; the buffer is held while a prefix of some longer rule could still match, and a chord timeout (flush-buffer) resolves a partial sequence that has gone quiet.
(defrule swap :canvas ((a :tap-object) (b :tap-object) :scratch) (ga gb gs)
(declare (ignore gs))
(rotatef (tt-object-x (cdr ga)) (tt-object-x (cdr gb))))(asdf:load-system :touchtoy)
(load "tests/test-classifier.lisp")
(touchtoy-tests:run-tests)Covers every primitive, scratch/half-scratch (kind under jitter + inner shape on clean geometry), multitouch (expand/shrink and the swipe/orbit rejections), tap-vs-modifier, and two seeded hit-rate tests for imperfect scratches and arrows.