# 01 — Layered Architecture Map This chapter walks Greenfield's architecture layer by layer, from the lowest abstraction (Wayland XML) up to the highest (the user-facing shell). Each layer is described in terms of: **responsibility**, **key abstractions**, **dependencies**, and **files of interest**. The chapter ends with an ASCII diagram showing how the layers stack and where data flows between them. ## The eight layers ``` ┌──────────────────────────────────────────────────────────────────────────┐ │ Layer 8: USER SHELL (Odoo 20 OWL addon, supplied by user) │ │ Hooks: UserShellApi (events + actions) │ │ ─ NOT covered in this chapter (see 08-app-lifecycle.md) │ ├──────────────────────────────────────────────────────────────────────────┤ │ Layer 7: DESKTOP / LAYOUT POLICY │ │ Floating vs AlwaysFullScreen, view stacking, focus activation refcount │ │ packages/compositor/src/desktop/Desktop.ts │ ├──────────────────────────────────────────────────────────────────────────┤ │ Layer 6: SURFACE LIFECYCLE / ROLES │ │ XdgWmBase, XdgSurface, XdgToplevel, XdgPopup, XdgPositioner, Shell, │ │ ShellSurface, Subcompositor, Subsurface, SurfaceRole │ │ packages/compositor/src/{Xdg*, Shell*, Sub*, SurfaceRole, SurfaceChild} │ ├──────────────────────────────────────────────────────────────────────────┤ │ Layer 5: RENDERING PIPELINE │ │ Surface (state stack), View, Output (WebGL canvas), Renderer, Scene, │ │ Shaders (YUV→RGBA, YUVA→RGBA, RGB+A→RGBA), Loop (RAF), Callback │ │ packages/compositor/src/{Surface, View, Output, Loop, Callback, Region} │ │ packages/compositor/src/render/ │ ├──────────────────────────────────────────────────────────────────────────┤ │ Layer 4: TRANSPORT (browser side) │ │ Channel (ARQ + Simple), ReconnectingWebSocket, RemoteAppLauncher, │ │ StreamingBuffer, BufferStream, EncodedFrame, EncoderFeedback, decoders │ │ (WebCodecs + WASM fallback) │ │ packages/compositor/src/remote/ │ ├──────────────────────────────────────────────────────────────────────────┤ │ Layer 4: TRANSPORT (native side) │ │ Channel (ARQ + Simple), SessionController, kcp.ts, NativeAppContext, │ │ FrameFeedback, SurfaceBufferEncoding, wayland-server.ts │ │ packages/compositor-proxy/src/{Channel, kcp, Session*, Native*, ...} │ ├──────────────────────────────────────────────────────────────────────────┤ │ Layer 3: WAYLAND RUNTIME (browser side) │ │ Connection (wire framing), Client, WlObject registry, marshallers │ │ libs/common/src/Connection.ts │ │ libs/compositor-protocol/src/{westfield-runtime-server.ts, generated/*} │ ├──────────────────────────────────────────────────────────────────────────┤ │ Layer 3: WAYLAND RUNTIME (native side) │ │ wayland-server.ts + wayland-server-addon.node (N-API → libwayland-server)│ │ MessageInterceptor (per-resource request/event routing) │ │ packages/compositor-proxy/src/{wayland-server.ts, protocol/*_interceptor}│ ├──────────────────────────────────────────────────────────────────────────┤ │ Layer 2: PROTOCOL GENERATORS │ │ client-generator, compositor-generator, compositor-proxy-generator │ │ libs/{client,compositor,compositor-proxy}-generator/ │ ├──────────────────────────────────────────────────────────────────────────┤ │ Layer 1: WAYLAND PROTOCOL XML │ │ protocol/{wayland, xdg-shell, wayland-drm, web-bitmapbuf-unstable-v1}.xml│ └──────────────────────────────────────────────────────────────────────────┘ ``` ## Layer 1 — Wayland Protocol XML **Responsibility**: declare the protocol surface — what interfaces, requests, events, enums, and bitfield types exist on the wire. **Files of interest**: - `protocol/wayland.xml` — standard upstream Wayland core. 22 interfaces, `wl_compositor` v4, `wl_seat` v6, `wl_surface` v4 (roughly Wayland 1.20-ish). Kristian Høgsberg copyright. Used by all 3 generators. - `protocol/xdg-shell.xml` — stable `xdg_shell` v1, 5 interfaces (xdg_wm_base, xdg_surface, xdg_toplevel, xdg_popup, xdg_positioner). Used by all 3 generators. - `protocol/wayland-drm.xml` — legacy `wl_drm` v2, used by native apps (Mesa, XWayland) to advertise dmabuf buffers. **Only the proxy generator processes this file** — so the proxy can intercept `create_buffer` / `create_planar_buffer` / `create_prime_buffer` and turn dmabuf fds into ProxyFDs for the browser's GStreamer encoding pipeline. Browser-side libs do not generate `wl_drm` stubs. - `protocol/web-bitmapbuf-unstable-v1.xml` — **Greenfield's custom extension**. Single interface `web_bitmapbuf_factory` v1, single request `create_buffer(id: new_id, bitmap: fd)`. Lets browser JS apps hand a GPU-backed `ImageBitmap` directly to the compositor as a `wl_buffer` — bypassing SHM copy, encoding, and dmabuf import. The `fd` arg is marshalled via the same `fileDescriptor(...)` path as `wl_shm.create_pool`'s fd, but holds an `ImageBitmap` (becomes a transferable on the flush path). **Not processed by the proxy generator** — native apps never use it. **Dependencies**: none. This is the source of truth. ## Layer 2 — Protocol Generators **Responsibility**: parse the XML with `xml2js`, walk the interface tree, emit typed TypeScript bindings for each side. **Key abstractions**: `ProtocolParser.mjs` (shared XML walker) + `ProtocolArguments.mjs` (per-side argument-type dispatch table). **Files of interest**: - `libs/client-generator/{ProtocolParser, ProtocolArguments, index}.mjs` — emits TS `*Proxy` classes for browser apps that want to be Wayland clients of the browser compositor (via `MessagePort`). - `libs/compositor-generator/{ProtocolParser, ProtocolArguments, index}.mjs` — emits TS `*Resource` + `*Requests` classes for the browser-resident compositor itself (receives requests, dispatches to `*Requests` impls, emits events). Skips `wl_display`, `wl_registry` (hand-written in runtime). - `libs/compositor-proxy-generator/{EndpointProtocolParser, EndpointProtocolArguments, index}.mjs` — emits CommonJS `*_interface.js` (wl_interface struct definitions for the N-API addon) + `*_interceptor.js` (per-opcode interceptors that decide routing) for the Node.js native proxy. **Why three generators?** Because each side has a fundamentally different job: 1. **client-generator** → browser apps that want to be Wayland clients of the browser compositor (via `MessagePort`). 2. **compositor-generator** → the browser-resident compositor itself (receives requests, dispatches to `*Requests` impls, emits events). 3. **compositor-proxy-generator** → the Node.js native proxy bridging real libwayland-client apps to the browser. Emits `wl_interface` struct definitions for the N-API addon + per-opcode interceptors that decide routing. **Dependencies**: `xml2js`, `camelcase`, `uppercamelcase`, `meow`. ## Layer 3 — Wayland Runtime **Responsibility**: marshal and unmarshal the actual wire bytes. Implement object lifecycle (creation, dispatch, destruction), new-id tracking, listener dispatch, file descriptor handling. **Browser side** (`libs/common/src/Connection.ts`) — the shared runtime for both `@gfld/client-protocol` and `@gfld/compositor-protocol`: - Wire format is **standard upstream Wayland**, byte-for-byte. 8-byte header: `u32 object_id | u16 opcode | u16 size-incl-header`, then per-arg bytes. `u/i/f/n/o` are 4 bytes; `s` and `a` are length-prefixed u32 + payload padded to 4-byte alignment; `h` (FD) takes zero bytes — FDs travel out-of-band in `wireMsg.fds[]`. - Reading: `wlObject[opcode](message)` — the generated classes use computed-property-key methods `[0](message) { ... }`, `[1](message) { ... }` so opcode is the array index. - Client side: `Proxy.marshallConstructor` calls `display.generateNextId()` (1-indexed, recycles ids from `wl_display.delete_id`), constructs the new Proxy (self-registers in constructor), patches the `new_id` arg's `value`, then flushes. - Server side: `Client.marshallConstructor` allocates from `SERVER_OBJECT_ID_BASE = 0xff000000` (or recycles locally). When a client-allocated id is destroyed, the server sends `wl_display.delete_id` so the client can recycle. - Connection registry: `wlObjects: { [id]: WlObject }`; `registerWlObject` throws on collision. - `Connection.message` dispatches by literal `wlObject[opcode](message)`. Each handler returns void or a Promise; `Connection` awaits the promise before reading the next message — this preserves wire-order while still allowing async implementations. Per-client Connections mean a slow client doesn't block others. **Native side** (`packages/compositor-proxy/src/wayland-server.ts`) — a TS wrapper around `wayland-server-addon.node` (the C N-API addon that wraps a **patched** `libwayland-server`): - `MessageInterceptor` — per-resource request/event dispatch. Each `wl_*_interceptor.js` file declares `R(...args)` and `E(...args)` functions that decide whether a message is routed `{native: true}` (handle in the proxy), `{browser: true}` (forward to browser compositor), or both. - Special cases: `wl_display.sync` → both native and browser (must complete before `done` event), `wl_display.get_registry` → both, `wl_registry.bind` → native if id is in `nativeGlobalNames[]` (e.g. `wl_drm`, `wl_shm`), else browser. Fast-sync optimization skips browser round trip when no prior browser-bound request occurred. **File descriptor handling — the Greenfield innovation**: `type FD = unknown` — it's intentionally opaque. Browser-side FDs are **transferable JS objects**: - `ArrayBufferView` for `wl_shm.create_pool`'s `fd` (SHM backing memory lives in browser, zero-copy transferred via `MessagePort.postMessage(..., transferList)`). - `ImageBitmap` for `web_bitmapbuf_factory.create_buffer`'s `bitmap` arg (GPU-backed texture, zero-copy). - `MessagePort` for `wl_data_offer.receive`'s `fd` (in-browser pipe). Native-side (proxy) FDs are real POSIX ints. `NativeWaylandClientSession.onWireMessageEnd` converts each to a JSON `ProxyFD = {handle, type, host}` (host = proxy's HTTP base URL), length-prefixed + 4-byte aligned, prepended to the wire message before sending over WebSocket/KCP. Browser's `webFS` resolves them by fetching data over HTTP. **Dependencies**: Layer 2's generated bindings + `@gfld/common`. ## Layer 4 — Transport **Responsibility**: carry wire bytes between the browser compositor and the native proxy. Multiplex logical channels. Handle reconnection, backpressure, and latency. **Browser side** (`packages/compositor/src/remote/`): - `Channel.ts` — defines `ARQChannel` (KCP-wrapped, reliable, low-latency) and `SimpleChannel` (raw WebSocket). - `ReconnectingWebSocket` — vendored from `pladaria/reconnecting-websocket` with `minDelay=1-5s`, `factor=1.3`, `maxDelay=10s`, `minUptime=5s`, `connTimeout=4s`, `retries=∞`. - `RemoteAppLauncher.ts` — sets up the 4 logical channels per app client (PROTOCOL, FRAME, XWM, FEEDBACK) and routes incoming messages. - `RemoteInputOutput.ts` — implements the `InputOutput` interface for native apps, with `mkfifo`, `mkstempMmap`, `readFd`, `writeFdAsStream`, `closeFd` over separate WebSocket endpoints. - `StreamingBuffer.ts`, `BufferStream.ts`, `EncodedFrame.ts`, `EncoderFeedback.ts` — decode pipeline for incoming H.264 / PNG frames. - `webcodec-buffer-decoder.ts`, `wasm-buffer-decoder.ts`, `H264NALDecoder.worker.ts` — the actual decoders. **Native side** (`packages/compositor-proxy/src/`): - `Channel.ts` — mirror image of the browser side. `ARQChannel` and `SimpleChannel`. - `kcp.ts` — ~1100-line TypeScript port of the canonical C `ikcp.c`. - `SessionController.ts` — WS server with 9 endpoint paths: `/signal`, `/channel`, `/mkfifo`, `/mkstemp-mmap`, `/read-fd`, `/read-fd-as-stream`, `/write-fd-as-stream`, `/close-fd`, `/request-keyframe`. - `Session.ts`, `SessionProcess.ts`, `SessionController.ts` — process model: one main process, one forked child per browser session. - `NativeAppContext.ts` — per-app state, signaling WS, lifecycle (10-min SIGHUP grace on disconnect, SIGTERM→SIGKILL escalation). - `FrameFeedback.ts` — receives 4-byte feedback messages from the browser, uses them to pace `wl_frame.done` callbacks. - `SurfaceBufferEncoding.ts`, `ProxyBuffer.ts` — encoding pipeline. - `io/` subdirectory — WebSocket server glue. - `encoding/` subdirectory — high-level encoding orchestration. **Wire format**: see `04-transport.md` for the full byte-level breakdown of PROTOCOL, FRAME, XWM, and FEEDBACK channels. **Dependencies**: Layer 3 + `ws` (Node) + browser-native WebSocket + `ReconnectingWebSocket`. ## Layer 5 — Rendering Pipeline **Responsibility**: take incoming buffer frames (SHM or encoded), turn them into pixels on a browser canvas/WebGL context. Manage surface state, damage tracking, frame callbacks, and the compositing loop. **Key abstractions**: - `Surface.ts` — the `wl_surface` implementation. Double-buffered state (`state` vs `pendingState`), atomic merge in `commitPending()`. Subsurface sync mode cascades through the ancestor chain. - `SurfaceChild.ts`, `SurfaceRole.ts`, `Subsurface.ts`, `Subcompositor.ts` — surface tree / role system. - `View.ts` — represents a surface's position on an output, used for compositing. - `Output.ts` — the `wl_output` implementation; how output is rendered to a WebGL canvas. - `Shm.ts` — `wl_shm` / shm pool handling. How shared memory buffers arrive in the browser. - `BufferContents.ts`, `BufferImplementation.ts`, `ImageBitmapBuffer.ts` — buffer abstraction hierarchy. - `Region.ts` — `wl_region`, damage regions (pixman-WASM backed). - `Callback.ts` — frame callback mechanism. - `Loop.ts` — the compositor main loop / `requestAnimationFrame` integration. - `Compositor.ts` — the `wl_compositor` global; how it creates surfaces and regions. - `Session.ts` — top-level browser-side session. - `Globals.ts` — how Wayland globals are registered. - `render/` subdirectory — the WebGL renderer: `Renderer.ts`, `Scene.ts`, `SceneShader.ts`, `Texture.ts`, `RenderState.ts`, `Program.ts`, `ShaderCompiler.ts`, `ShaderSources.ts`, `YUVA2RGBA.ts`, `YUVA2RGBAShader.ts`, `YUV2RGBShader.ts`, `RGBXandAToRGBA.ts`, `RGBandA2RGBAShader.ts`. - `browser/` subdirectory — browser integration glue (canvas, RAF, ResizeObserver, dnd, selection, input). - `math/` subdirectory — matrix/region math (`Mat4.ts`, `Rect.ts`). **Pipeline**: see `03-html5-rendering.md` for the full commit → damage → render → present pipeline. **Dependencies**: Layer 4 + WebGL + WebCodecs + `@gfld/compositor-wasm` (pixman + xkbcommon). ## Layer 6 — Surface Lifecycle / Roles **Responsibility**: implement the role-specific surface state machines (xdg-toplevel, xdg-popup, xdg-positioner, wl_shell_surface, subsurface). Manage configure/ack-configure round-trips, popup grabs, window state. **Files of interest**: - `XdgWmBase.ts` — the `xdg_wm_base` global. How clients advertise they're XDG clients. Includes ping-based unresponsiveness detection. - `XdgSurface.ts` — `xdg_surface`. The role wrapper around a `wl_surface`. - `XdgToplevel.ts` — `xdg_toplevel`. Top-level windows. State (maximized, fullscreen, minimized, activated, resizing). Configure/ack-configure round-trip. - `XdgPopup.ts` — `xdg_popup`. Transient windows, menus, tooltips. Grab semantics. - `XdgPositioner.ts` — `xdg_positioner`. How popups are positioned relative to parent. - `Shell.ts`, `ShellSurface.ts` — the legacy `wl_shell` (deprecated but still implemented for old clients). - `Subcompositor.ts`, `Subsurface.ts`, `SurfaceChild.ts`, `SurfaceRole.ts` — subsurface compositing, the surface tree. **State machines**: see `08-app-lifecycle.md` for the full XdgToplevel lifecycle, configure/ack round-trip semantics, popup positioner math, and subsurface sync/desync mode. **Dependencies**: Layer 5 + Layer 3 (Wayland runtime). ## Layer 7 — Desktop / Layout Policy **Responsibility**: arrange surfaces on the output. Decide stacking order, focus policy, and the high-level window management mode (Floating vs AlwaysFullScreen). **Files of interest**: - `desktop/Desktop.ts` — implements both `FloatingDesktopSurface` and `AlwaysFullScreenDesktopSurface`, chosen by `session.config.mode`. Floating lets the user move/resize windows freely; AlwaysFullScreen enforces one window per output (kiosk mode). **Dependencies**: Layer 6 + Layer 5 (for View rendering). ## Layer 8 — User Shell **Responsibility**: the actual user-facing UI. The taskbar, window decorations, app launcher, keyboard layout picker, etc. **Out of scope for the user** — they will build their own shell as an Odoo 20 OWL addon. The integration point is `UserShellApi.ts` (see `08-app-lifecycle.md`). **Greenfield's reference shell** (`packages/compositor-shell`) is a Preact + Tailwind SPA, 562 lines of TypeScript total. Mentioned here only for orientation; not analyzed in this bundle. ## How data flows across the layers ``` NATIVE APP (e.g. gtk4-demo, X11 via XWayland) │ │ libwayland-client → UNIX socket + SCM_RIGHTS FDs ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ NATIVE PROXY (Node.js + C N-API addons) │ │ │ │ Layer 3 (native): wayland-server.ts + wayland-server-addon.node │ │ ↳ patched libwayland-server parses wire bytes │ │ ↳ MessageInterceptor routes each request to {native, browser} │ │ ↳ FDs wrapped as ProxyFD JSON │ │ │ │ Layer 4 (native): Channel.ts + kcp.ts + SessionController │ │ ↳ 4 logical channels: PROTOCOL, FRAME, XWM, FEEDBACK │ │ ↳ KCP-over-WebSocket for ARQ channels │ │ ↳ SimpleChannel for FEEDBACK │ │ │ │ Layer 5 (encoding): SurfaceBufferEncoding + proxy-encoding-addon │ │ ↳ GStreamer H.264 (x264 / nvh264 / vaapih264) on dedicated thread │ │ ↳ PNG fallback for ≤256×256 surfaces │ │ ↳ Zero-copy dmabuf via EGLImage → GstGLMemoryEGL │ │ │ │ Layer 1 (XML) → Layer 2 (proxy-generator) feeds Layer 3 interceptors │ └─────────────────────────────────────────────────────────────────────────┘ │ │ WebSocket (TCP) + KCP-over-WebSocket │ 9 endpoint paths per session ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ BROWSER COMPOSITOR (TypeScript + WebGL + WebCodecs + WASM) │ │ │ │ Layer 4 (browser): Channel.ts + RemoteAppLauncher + decoders │ │ ↳ ReconnectingWebSocket with exponential backoff │ │ ↳ ARQChannel + SimpleChannel mirror the native side │ │ ↳ WebCodecs VideoDecoder preferred, FFmpeg WASM fallback │ │ ↳ H264NALDecoder.worker.ts (2 workers: opaque + alpha) │ │ │ │ Layer 3 (browser): @gfld/common Connection + compositor-protocol │ │ ↳ Standard Wayland wire format, byte-for-byte │ │ ↳ WlObject registry, opcode dispatch via wlObject[opcode](msg) │ │ ↳ Per-client Connections (slow client never blocks another) │ │ │ │ Layer 5: Surface.ts + View.ts + Output.ts + render/ │ │ ↳ Double-buffered state (state vs pendingState) │ │ ↳ Damage tracking via pixman-WASM regions │ │ ↳ WebGL1 with FBOs for YUV→RGBA, RGB+Alpha→RGBA conversion │ │ ↳ desynchronized:true canvas for low-latency presentation │ │ ↳ Frame callbacks fired on requestAnimationFrame vsync │ │ │ │ Layer 6: XdgWmBase + XdgSurface + XdgToplevel + XdgPopup + ... │ │ ↳ Configure/ack-configure round-trips │ │ ↳ Popup positioner math (anchor + gravity + constraints) │ │ ↳ Subsurface sync/desync mode cascading │ │ │ │ Layer 7: desktop/Desktop.ts │ │ ↳ Floating vs AlwaysFullScreen mode │ │ ↳ View stacking, focus refcount │ │ │ │ Layer 8: UserShellApi (events + actions) │ │ ↳ Events: clientCreated, surfaceCreated, surfaceTitleUpdated, │ │ surfaceActivationUpdated, ... │ │ ↳ Actions: initScene, refreshScene, destroyScene, │ │ closeClient, activateSurface, setUserConfiguration │ │ ↳ THE INTEGRATION SEAM — Odoo 20 OWL shell hooks here │ └─────────────────────────────────────────────────────────────────────────┘ │ │ UserShellApi.events.* fired by compositor │ UserShellApi.actions.* called by shell ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ USER SHELL (Odoo 20 OWL addon — supplied by user) │ │ │ │ - Application launcher (lists apps from --applications JSON) │ │ - Window management UI (taskbar, decorations, minimize/maximize) │ │ - Keyboard layout picker (RMLVO) │ │ - Per-output canvas (HTMLCanvasElement, one per Output) │ │ - Trigger app launches via fetch('http://proxy:8081/') │ │ - Open signalURL WebSocket to receive CONNECT_CHANNEL messages │ │ │ │ Not analyzed in this bundle (out of scope). │ └─────────────────────────────────────────────────────────────────────────┘ ``` ## Reading guide for the rest of the bundle Now that you've seen the layer map, the remaining chapters drill into each layer in detail: - `02-wayland-protocol.md` — Layers 1, 2, 3 (XML → generators → runtime) - `03-html5-rendering.md` — Layer 5 (rendering pipeline) - `04-transport.md` — Layer 4 (transport, both sides) - `05-input-handling.md` — Layer 5 input subsystem (cross-cuts Layers 3, 4, 5) - `06-data-clipboard.md` — Layer 5 data device (cross-cuts Layers 3, 4, 5) - `07-native-app-bridge.md` — Layer 4 native + Layer 3 native + Layer 1 XML native (proxy-only `wl_drm`) - `08-app-lifecycle.md` — Layer 6 + Layer 8 (UserShellApi seam) - `09-hardware-accel.md` — Layer 5 encoding + Layer 4 frame channel + Layer 3 WASM - `10-porting-to-odoo-js-python.md` — how to rebuild all 8 layers in your stack - `11-pitfalls.md` — what Greenfield does wrong, layer by layer - `12-appendix.md` — glossary, file index, external references