Skip to content

Latest commit

 

History

81 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

loom

loom is the interactive browser shell for weft, a web engine written in pure Common Lisp. weft does the whole pipeline — fetch, HTML parse, CSS cascade, JavaScript, layout, paint to an RGB8 canvas. loom is the shell: it holds a persistent page model (load → render → dispatch DOM events → re-render) and drives it onto a screen through glass, a pure-Common-Lisp framebuffer + VNC (RFB) server.

The whole stack is FFI-free. weft paints a row-major RGB8 canvas; glass ships 0x00RRGGBB framebuffers over RFB; loom's glass driver packs one into the other and turns RFB pointer/key events into DOM events weft already dispatches. No SDL, no X, no C library — the entire weaving stack (scribe / shuttle / gesso / stencil / weft / loom) is pure Common Lisp end to end. Point any VNC client at it.

   ┌────────── loom (this repo) ──────────┐        ┌──── weft (pure CL) ────┐
   │  page model · input · glass driver    │  <-->  │  fetch → HTML → CSS →   │
   │  (pure Common Lisp — no FFI)          │        │  JS → layout → paint    │
   └───────────────────────────────────────┘        └─────────────────────────┘
              │  glass framebuffer + RFB (VNC)
              ▼
        any VNC client

What it does

  • Live display over VNC. weft renders to a tall RGB8 canvas; loom packs the visible slice (at the current scroll offset) into a glass framebuffer, and glass ships only the dirty tiles to any connected VNC client.
  • Browse. Mouse-wheel scrolling pans the page canvas. Clicking an <a href> resolves the URL against the page base, fetches it through weft, and renders a fresh scripted page in place.
  • Live JavaScript. Hit-testing maps a pixel to the deepest DOM node (inline links/buttons included); RFB pointer/key events become trusted DOM events routed through weft's dispatchEvent (mousedown / mouseup / click, mousemove / mouseover / mouseout with a hover cursor, keydown). weft's timer/macrotask clock is pumped each frame, so setTimeout animations run. A page with an onclick or hover handler visibly reacts.

Running it

# a Common Lisp with Quicklisp (SBCL recommended), plus weft + its engine
# siblings + glass visible to ASDF/Quicklisp, e.g.
#   ln -s /path/to/loom  ~/quicklisp/local-projects/loom
#   ln -s /path/to/weft  ~/quicklisp/local-projects/weft   (and scribe/shuttle/gesso/stencil)
#   ln -s /path/to/glass ~/quicklisp/local-projects/glass

sbcl --control-stack-size 256 --dynamic-space-size 4096 \
     --eval '(ql:quickload :loom/glass)' \
     --eval '(loom.glass:run-glass :port 5900)'            # the bundled home page
# or a URL / local file:
sbcl --control-stack-size 256 --dynamic-space-size 4096 \
     --eval '(ql:quickload :loom/glass)' \
     --eval '(loom.glass:run-glass :start "https://example.com" :port 5900)'

Then point a VNC viewer at localhost:5900. :background t returns immediately (the RFB server and repaint pump run in their own threads).

Or with the helper script:

./run.sh                              # bundled home page, VNC on :5900
./run.sh https://example.com          # a URL
./run.sh path/to/page.html            # a local file
./run.sh https://example.com 5901     # a URL on a chosen VNC port

How it's built

file role
src/page.lisp the persistent page model: load, render, hit-test, dispatch, scroll, navigate — display-agnostic
src/input.lisp pure pointer/wheel → DOM translation + scroll / URL math
src/main.lisp start-page + file:// → path helpers a driver needs to open its first page
src/glass-shell.lisp the glass driver: pack weft's canvas into a glass framebuffer, wire RFB input to the page model, run the repaint/timer pump (loom/glass system)

The correctness-critical logic lives in input.lisp + page.lisp and is fully unit-tested headlessly (inspect/tests.lisp): hit-testing, event translation, scroll clamping, URL resolution, and a full load → click → observe-the-DOM-react cycle. inspect/forms-interact.lisp gates the interactive form path by driving the shell the way a person does — clicking at pixel coordinates, typing — and asserting on both the DOM and the pixels that actually got painted. (weft's ~2,800-subtest forms oracle only ever reads the DOM, so none of that path was visible to it.) inspect/smoke.lisp exercises the glass display path end-to-end without a VNC client (attach a page to a framebuffer, pump the loop, write the render PNG).

weft's JS context is single-threaded, so the glass driver serialises the RFB client thread against the timer/repaint pump with one mutex.

Tests

# headless gates: pure logic + page model, and the form-interaction gate
sbcl --eval '(asdf:test-system "loom")'

# glass smoke test (load a real render, attach to a framebuffer, pump, write a PNG)
sbcl --script inspect/smoke.lisp

inspect/glass-demo.lisp closes the loop headlessly: it serves the bundled home page over VNC, sends a real RFB click on a link with an in-process RFB client, and confirms the page navigates and re-renders — no display required. inspect/glass-forms.lisp does the same for interaction: it serves a form, then clicks and types over the wire as real RFB PointerEvent / KeyEvent messages — focus, .value, a ticked checkbox, a dropdown opened and picked from, and a submit that reads them all back. The headless gates drive the page model directly, so only this covers the layer above it (chrome offsets, keysym translation, the repaint pump).

Status / not yet

  • Form controls are interactive: clicking activates them (a checkbox toggles, a radio takes its group, a submit button submits), clicking focuses and places the caret, and typing edits the value with input/change fired where HTML says. Text is selectable — shift-arrows extend, dragging the pointer selects the columns crossed, the selection paints, and typing replaces it — and Tab / Shift-Tab walk the focusable controls in HTML's order (positive tabindex first, disabled and tabindex=-1 skipped), selecting the value on the way in. A <select> opens a real dropdown — painted over the page (it has no layout box), picked with the pointer or the arrow keys, dismissed by Escape or a click outside.
  • CSS :hover restyling (recascading on hover) is deferred; JS hover handlers (mouseover / mouseout) and the pointer cursor already work.

MIT licensed. The engine it drives (weft and siblings) is pure Common Lisp.

About

The interactive browser shell for weft — drives the weft web engine over glass (pure-CL VNC). No SDL, no FFI.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages