Files

17 KiB

01 — Layered Architecture Map

This chapter walks the proposed MIT-licensed architecture layer by layer, mirroring the Greenfield analysis structure (see ../greenfield-analysis/01-architecture.md) so you can compare them side-by-side.

The eight layers (MIT approach)

┌──────────────────────────────────────────────────────────────────────────┐
│ Layer 8: USER SHELL (Odoo 20 OWL addon, PROPRIETARY)                      │
│   Hooks: UserShellApi (events + actions) — clean-room spec                │
│   See 10-odoo-integration.md                                              │
├──────────────────────────────────────────────────────────────────────────┤
│ Layer 7: DESKTOP / LAYOUT POLICY (PROPRIETARY TS, clean-room)              │
│   Floating vs AlwaysFullScreen, view stacking, focus activation refcount   │
│   Custom: desktop/Desktop.ts (~800 LOC)                                   │
├──────────────────────────────────────────────────────────────────────────┤
│ Layer 6: SURFACE LIFECYCLE / ROLES (PROPRIETARY TS, clean-room)            │
│   XdgWmBase, XdgSurface, XdgToplevel, XdgPopup, XdgPositioner, Shell,      │
│   ShellSurface, Subcompositor, Subsurface, SurfaceRole                     │
│   Custom: roles/*.ts (~1,800 LOC)                                         │
├──────────────────────────────────────────────────────────────────────────┤
│ Layer 5: RENDERING PIPELINE (PROPRIETARY TS, clean-room, modernized)       │
│   Surface (state stack), View, Output (WebGL2 canvas), Renderer, Scene,    │
│   Shaders (YUV→RGBA, YUVA→RGBA, RGB+A→RGBA), Loop (RAF), Callback          │
│   NEW: OffscreenCanvas + Web Workers + WebGL2 (Greenfield uses WebGL1)     │
│   Custom: render/*.ts (~1,800 LOC), surface/*.ts (~1,200 LOC)             │
├──────────────────────────────────────────────────────────────────────────┤
│ Layer 4: TRANSPORT (browser side, PROPRIETARY TS, clean-room)              │
│   Channel (ARQ + Simple), ReconnectingWebSocket, RemoteAppLauncher,       │
│   StreamingBuffer, BufferStream, EncodedFrame, EncoderFeedback, decoders    │
│   NEW: WebCodecs VideoDecoder replaces FFmpeg WASM (was GPL)               │
│   Custom: transport/*.ts (~3,500 LOC), decoders/*.ts (~500 LOC)           │
├──────────────────────────────────────────────────────────────────────────┤
│ Layer 4: TRANSPORT (Python side, PROPRIETARY, clean-room)                  │
│   aiohttp WebSocket server (9 endpoints), asyncio + cffi shim,            │
│   kcp PyPI package, multiprocessing.Process per session, JWT auth           │
│   Custom: proxy/*.py (~2,880 LOC)                                          │
├──────────────────────────────────────────────────────────────────────────┤
│ Layer 3: WAYLAND RUNTIME (browser side)                                    │
│   REUSED: @gfld/common (Connection, WlObject, Fixed, marshallers) — MIT   │
│   REUSED: @gfld/compositor-protocol (Display, Client, Resource, Global)   │
│           — MIT, verified NO @gfld/compositor import                       │
├──────────────────────────────────────────────────────────────────────────┤
│ Layer 3: WAYLAND RUNTIME (Python side)                                     │
│   REUSED: pywayland (Apache-2.0) — Display, Client, Resource, Global       │
│   + 5-line cffi shim for wl_event_loop_get_fd (asyncio integration)        │
│   + Greenfield's changes.diff (MIT patches) applied to libwayland-server   │
├──────────────────────────────────────────────────────────────────────────┤
│ Layer 2: PROTOCOL GENERATORS (REUSED)                                       │
│   REUSED: @gfld/client-generator, @gfld/compositor-generator (MIT)         │
│   REUSED: pywayland-protocols (auto-generated from wayland-protocols repo) │
├──────────────────────────────────────────────────────────────────────────┤
│ Layer 1: WAYLAND PROTOCOL XML (REUSED)                                      │
│   REUSED: wayland.xml, xdg-shell.xml, wayland-drm.xml (upstream MIT)       │
│   REUSED: web-bitmapbuf-unstable-v1.xml (Greenfield's custom extension)    │
└──────────────────────────────────────────────────────────────────────────┘

Side-by-side comparison with Greenfield

Layer Greenfield MIT approach
Layer 8: User Shell @gfld/compositor-shell (AGPL, Preact) Custom Odoo 20 OWL addon (proprietary)
Layer 7: Desktop policy desktop/Desktop.ts (AGPL) Clean-room rewrite (~800 LOC TS)
Layer 6: Surface roles Xdg*.ts, Shell*.ts, Sub*.ts (AGPL) Clean-room rewrite (~1,800 LOC TS)
Layer 5: Rendering render/*.ts, Surface.ts, etc. (AGPL, WebGL1, main-thread) Clean-room rewrite (~3,000 LOC TS, WebGL2 + OffscreenCanvas + Workers)
Layer 4: Transport (browser) remote/*.ts (AGPL) + FFmpeg WASM (GPL) Clean-room rewrite (~3,500 LOC TS) + WebCodecs (W3C standard)
Layer 4: Transport (host) compositor-proxy/src/*.ts (AGPL) + 3 N-API addons Clean-room Python rewrite (~2,880 LOC Python, NO native addons)
Layer 3: Wayland runtime (browser) @gfld/common + @gfld/compositor-protocol (MIT) REUSED AS-IS
Layer 3: Wayland runtime (host) wayland-server-addon.node (AGPL, 1231 lines C, patched libwayland) REPLACED with pywayland (Apache-2.0) + 5-line cffi shim + Greenfield's changes.diff (MIT patches)
Layer 2: Generators @gfld/*-generator (MIT) REUSED AS-IS + pywayland-protocols (auto-generated)
Layer 1: Protocol XML protocol/*.xml (upstream MIT + Greenfield's web-bitmapbuf) REUSED AS-IS

How data flows across the layers

                    NATIVE APP (e.g. gtk4-demo, X11 via XWayland)
                        │
                        │ libwayland-client → UNIX socket + SCM_RIGHTS FDs
                        ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ PYTHON PROXY (aiohttp + pywayland + GStreamer + bubblewrap)              │
│                                                                         │
│   Layer 3 (Python): pywayland + 5-line cffi shim                        │
│       ↳ VANILLA libwayland-server (NO patches needed for basic use)     │
│       ↳ Apply Greenfield's changes.diff (MIT) IF we want wire-message   │
│         interception hooks (recommended for the fast-sync optimization)  │
│       ↳ Per-resource dispatchers (auto-generated by pywayland scanner)  │
│       ↳ FDs wrapped as ProxyFD JSON                                      │
│                                                                         │
│   Layer 4 (Python): aiohttp WebSocket + kcp PyPI package                 │
│       ↳ 9 endpoints: /signal, /channel, /mkfifo, /mkstemp-mmap,         │
│         /read-fd, /read-fd-as-stream, /write-fd-as-stream,              │
│         /close-fd, /request-keyframe                                     │
│       ↳ asyncio integrates libwayland FD via loop.add_reader()           │
│       ↳ multiprocessing.Process per Odoo session (fork)                  │
│       ↳ JWT validation (pyjwt)                                           │
│                                                                         │
│   Layer 5 (encoding): PyGObject + GStreamer                              │
│       ↳ gst_parse_launch: same pipeline strings as Greenfield            │
│       ↳ x264enc REPLACED with vaapih264enc / nvh264enc / openh264enc     │
│         (x264enc is GPL — see 11-pitfalls.md)                            │
│       ↳ Zero-copy dmabuf via EGLImage → GstGLMemoryEGL (same as Greenfield)│
│       ↳ GLib MainLoop in separate Python thread                          │
│       ↳ asyncio.run_coroutine_threadsafe for cross-thread marshalling    │
│                                                                         │
│   Layer 7 (sandboxing): bubblewrap + seccomp + systemd-run --scope     │
│       ↳ Per-app: MemoryMax=1G, CPUQuota=200%, TasksMax=256              │
│       ↳ --unshare-all, --clearenv, --ro-bind /usr /usr                  │
│       ↳ --dev-bind /dev/dri/renderD128 /dev/dri/renderD128 (single GPU) │
│       ↳ --ro-bind $HOST_XDG/wayland-N /run/user/<UID>/wayland-N          │
│       ↳ --uid <100000-165535 per-app> --gid <same>                       │
│       ↳ --unshare-net by default                                         │
│                                                                         │
│   Layer 1 (XML) → Layer 2 (pywayland scanner) feeds Layer 3 dispatchers  │
└─────────────────────────────────────────────────────────────────────────┘
              │
              │ WebSocket (TCP) + KCP-over-WS, JWT auth
              │ Same-origin via Nginx reverse proxy
              ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ BROWSER COMPOSITOR (Custom TypeScript + WebGL2 + WebCodecs + WASM)      │
│                                                                         │
│   Layer 4 (browser): Custom Channel + KCP + ReconnectingWebSocket        │
│       ↳ ARQChannel + SimpleChannel (clean-room, ~250 LOC)                │
│       ↳ KCP ported fresh from MIT C reference (skywind3000/kcp)          │
│       ↳ ReconnectingWebSocket (vendored from pladaria, MIT)             │
│       ↳ WebCodecs VideoDecoder (primary, hardware-accelerated)           │
│       ↳ OpenH264 BSD Wasm (fallback, NOT FFmpeg — FFmpeg is GPL)         │
│                                                                         │
│   Layer 3 (browser): REUSED @gfld/common + @gfld/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: Custom Surface.ts + View.ts + Output.ts + render/            │
│       ↳ Double-buffered state (state vs pendingState)                    │
│       ↳ Damage tracking via @gfld/compositor-wasm pixman regions          │
│       ↳ WebGL2 with FBOs for YUV→RGBA, RGB+Alpha→RGBA conversion         │
│       ↳ OffscreenCanvas in Web Worker (NEW vs Greenfield)                │
│       ↳ Frame callbacks fired on requestAnimationFrame vsync             │
│                                                                         │
│   Layer 6: Custom XdgWmBase + XdgSurface + XdgToplevel + XdgPopup + ... │
│       ↳ Configure/ack-configure round-trips                              │
│       ↳ Popup positioner math (anchor + gravity + constraints)           │
│       ↳ Subsurface sync/desync mode cascading                            │
│       ↳ Primary selection (NEW — Greenfield lacks this)                  │
│                                                                         │
│   Layer 7: Custom desktop/Desktop.ts                                    │
│       ↳ Floating vs AlwaysFullScreen mode                                │
│       ↳ View stacking, focus refcount                                    │
│       ↳ Adaptive bitrate (NEW — consumes EncoderFeedback data)           │
│                                                                         │
│   Layer 8: UserShellApi (clean-room spec) + Odoo 20 OWL shell            │
│       ↳ Events: clientCreated, surfaceCreated, surfaceTitleUpdated,      │
│                 surfaceActivationUpdated, ...                             │
│       ↳ Actions: initScene, refreshScene, destroyScene,                  │
│                  closeClient, activateSurface, setUserConfiguration      │
│       ↳ OWL shell wires events to reactive state                         │
└─────────────────────────────────────────────────────────────────────────┘
              │
              │ userShell.events.* fired by compositor
              │ userShell.actions.* called by OWL shell
              ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ USER SHELL (Odoo 20 OWL addon — PROPRIETARY)                              │
│                                                                         │
│   - ir.actions.client with target=fullscreen                             │
│   - Application launcher (lists apps from Odoo /web/compositor/apps)    │
│   - Window management UI (taskbar, decorations, minimize/maximize)      │
│   - Keyboard layout picker (RMLVO)                                       │
│   - Per-output <canvas> element (OWL component owns the lifecycle)     │
│   - Triggers app launches via /web/compositor/launch                     │
│   - Receives JWT from /web/compositor/token                             │
│                                                                         │
│   Not a Greenfield component — entirely your code.                       │
└─────────────────────────────────────────────────────────────────────────┘

Reading guide for the rest of the bundle

  • 02-wayland-protocol.md — Layer 1, 2, 3 (XML → generators → runtime, both sides)
  • 03-html5-rendering.md — Layer 5 (custom WebGL2 + Workers + OffscreenCanvas)
  • 04-transport.md — Layer 4 (Python aiohttp + kcp, browser side clean-room)
  • 05-input-handling.md — Layer 5 input subsystem (Python xkbcommon, browser side)
  • 06-data-clipboard.md — Layer 5 data device (with primary selection, Greenfield lacks)
  • 07-native-app-bridge.md — Layer 4 Python + Layer 3 Python + sandboxing
  • 08-app-lifecycle.md — Layer 6 + Layer 8 (UserShellApi clean-room spec)
  • 09-hardware-accel.md — Layer 5 encoding + Layer 4 frame channel + WebCodecs
  • 10-odoo-integration.md — Layer 8 + Odoo 20 backend addon + JWT + Nginx + Docker
  • 11-pitfalls.md — 3 GPL contaminants found + clean-room legal requirements
  • 12-appendix.md — glossary, file index, references