# 00 — The MIT Approach at a Glance ## The one-paragraph summary Build a fully proprietary Wayland-to-HTML5 compositor for the Odoo 20 fork by **reusing Greenfield's 7 MIT-licensed libraries** as browser-side building blocks (the protocol runtime, the generators, the WASM modules for pixman+xkbcommon, the X11 bindings if XWayland is needed) and **replacing the 4 AGPL packages** (`@gfld/compositor`, `@gfld/compositor-proxy`, `@gfld/compositor-shell`, `@gfld/compositor-proxy-cli`) with **clean-room reimplementations** in TypeScript (browser) and Python (Linux host). The result integrates with Odoo 20 as a custom OWL shell + backend addon, with JWT-based auth and bubblewrap/seccomp app sandboxing — features Greenfield lacks entirely. ## What's reused (MIT/Apache/BSD — no AGPL contamination) ### Browser side — reused from Greenfield | Library | License | Purpose | |---|---|---| | `@gfld/common` | MIT | Wayland wire format codec (Connection, WlObject, Fixed, marshallers) | | `@gfld/client-protocol` | MIT | Browser-side Wayland client bindings (for browser-resident JS/WASM apps that want to be Wayland clients of OUR compositor) | | `@gfld/compositor-protocol` | MIT | Browser-side Wayland server bindings — the **strategic keystone lib**. Verified: imports ZERO code from `@gfld/compositor` (AGPL). | | `@gfld/client-generator` | MIT | Build-time CLI: XML → TS browser client bindings | | `@gfld/compositor-generator` | MIT | Build-time CLI: XML → TS browser server bindings | | `@gfld/compositor-wasm` | MIT | pixman 0.42.2 + xkbcommon 1.5.0 compiled to WASM | | `@gfld/xtsb` | MIT | X11 TypeScript bindings (only if XWayland support is needed) | ### Browser side — native APIs (W3C standards, MIT-equivalent) | API | Standard | Purpose | |---|---|---| | WebGL2 | W3C | GPU-accelerated 2D/3D rendering (replaces Greenfield's WebGL1) | | WebCodecs `VideoDecoder` | W3C | Hardware H.264/VP9/AV1 decode (replaces Greenfield's GPL FFmpeg WASM) | | OffscreenCanvas | W3C | Move WebGL to a Web Worker (Greenfield doesn't use this) | | Web Workers | W3C | Parallel decode + render (Greenfield uses only 2 workers for the fallback path) | | WebSocket | W3C | Binary transport (same as Greenfield) | | SharedArrayBuffer + Atomics | W3C | Zero-copy between workers (needs COOP/COEP headers) | | ImageBitmap | W3C | GPU-backed bitmap for the `web-bitmapbuf` fast path | | WebTransport | W3C (future) | UDP-over-QUIC transport (when browser support is widespread) | ### Linux side — Python ecosystem | Component | License | Purpose | |---|---|---| | pywayland | Apache-2.0 | Python bindings to libwayland-server (replaces Greenfield's `wayland-server-addon.node` + `wayland-server.ts`) | | python-xkbcommon (`xkbcommon` on PyPI) | MIT | Python bindings to libxkbcommon | | PyGObject (`gi.repository.Gst`) | LGPL-2.1+ | Python bindings to GStreamer (replaces Greenfield's `proxy-encoding-addon.node` + `libproxy-encoding.so`) | | GLib | LGPL-2.1+ | GStreamer runtime dep | | GStreamer core | LGPL-2.0+ | Media pipeline framework | | gst-plugins-base, gst-plugins-good | LGPL | `pngenc`, `appsrc`, `appsink`, `videoconvert` | | gst-vaapi | LGPL + MIT (libva) | `vaapih264enc` for Intel/AMD GPU encoding | | gst-plugins-bad (BSD subset) | BSD | `nvh264enc` for NVIDIA NVENC, `openh264enc` for software fallback | | libwayland | MIT | The Wayland protocol C library | | libdrm | MIT | Direct Rendering Manager | | mesa (libGL, libEGL, libgbm) | MIT | OpenGL/EGL/GBM for GPU buffer management | | aiohttp | Apache-2.0 | Async HTTP + WebSocket server | | kcp (PyPI, RealistikDash) | MIT | Cython wrapper over upstream `ikcp.c` | | PyJWT | MIT | JWT issuance + validation | | bubblewrap (bwrap) | LGPL-2.0+ | Unprivileged sandboxing | | libseccomp | LGPL-2.1 | Seccomp BPF filter compilation | ### Linux side — vendored Greenfield C files (MIT) These are the C files in `packages/compositor-proxy/native/wayland/src/` that are MIT-licensed (Greenfield's own work, NOT wlroots-derived unless noted) and can be **reused verbatim** in our Python proxy: | File | Lines | Purpose | |---|---|---| | `westfield-buffer.c` | ~150 | Buffer helpers | | `westfield-dmabuf.c` | ~200 | DMA-BUF import via EGL/GBM | | `westfield-egl.c` | ~900 | EGL zero-copy setup (creates EGLDisplay, EGLContext, wraps dmabuf as EGLImage) | | `westfield-xwayland.c` | ~465 | XWayland spawn + bridge | | `wlr_drm.c` | ~244 | `wl_drm` global impl (vendored from wlroots, MIT) | | `wlr_linux_dmabuf_v1.c` | ~1,110 | `zwp_linux_dmabuf_v1` global impl (vendored from wlroots, MIT) | | `drm_format_set.c` | ~239 | DRM format helpers (vendored from wlroots, MIT) | | `addon.c` | ~73 | wlroots addon system helper | | `changes.diff` | ~593 | Patches to libwayland-server adding wire-message interception hooks | **Total**: ~3,974 lines of MIT-licensed C that can be reused as-is. ## What's replaced (custom code, written clean-room) ### Browser side — custom TypeScript compositor library **Replaces**: `@gfld/compositor` (AGPL, ~21,101 LOC) | Component | Lines | Weeks | Notes | |---|---|---|---| | `surface/` (Surface, SurfaceRole, Subsurface, Subcompositor, Region, FrameCallback) | ~1,200 | 3 | Double-buffered state, commit semantics | | `render/` (Renderer, Scene, View, Output, RenderState, Texture, shaders) | ~1,800 | 3 | WebGL2 + OffscreenCanvas + Web Workers | | `buffer/` (Shm, BufferContents, ImageBitmapBuffer, EncodedFrameBuffer) | ~600 | 1.5 | SHM with BMP-header hack | | `transport/` (Channel, KCP, ReconnectingWebSocket, BufferStream, decoders) | ~3,500 | 4 | KCP ported fresh from MIT C ref | | `input/` (Seat, Keyboard, Pointer, Touch, Xkb, InputQueue, InputOutput) | ~2,500 | 3 | Uses @gfld/compositor-wasm | | `roles/` (XdgWmBase, XdgSurface, XdgToplevel, XdgPopup, XdgPositioner, Shell) | ~1,800 | 3 | xdg-shell state machine, biggest risk | | `data/` (DataDeviceManager, DataSource, DataOffer, WaylandDataSource) | ~600 | 1.5 | Clipboard + DnD + primary selection (Greenfield lacks primary) | | `desktop/` (Desktop, FloatingDesktopSurface, FullscreenDesktopSurface) | ~800 | 1.5 | Layout policy | | `math/` (Mat4, Vec4, Point, Size, Rect, Transformations) | ~500 | 0.5 | | | `userapi/` (UserShellApi) | ~120 | 0.5 | Odoo OWL shell integration seam | | `browser/` (input.ts, pointer.ts, dnd.ts, selection.ts) | ~750 | 2 | DOM glue | | `decoders/` (WebCodecsFrameDecoder, OpenH264FrameDecoder) | ~500 | 1 | NO FFmpeg Wasm (GPL) | | `core/` (Session, Globals) | ~200 | 0.5 | | | **MVP TOTAL** | **~13,400** | **~25 weeks** | | | `xwayland/` (XWindowManager, XWindow, XWindowFrame) | ~5,000 | 8 | Optional, only for X11 apps | | Tests + hardening | — | 3 | | | **GRAND TOTAL** | **~18,900** | **~30-36 weeks** | | ### Linux side — custom Python proxy **Replaces**: `@gfld/compositor-proxy` + `@gfld/compositor-proxy-cli` (AGPL, ~5,000 LOC TS + ~3,974 LOC C) | Component | Lines | Dev-days | Notes | |---|---|---|---| | aiohttp WS server (9 endpoints) | 600 | 3 | /signal, /channel, /mkfifo, /mkstemp-mmap, /read-fd, /read-fd-as-stream, /write-fd-as-stream, /close-fd, /request-keyframe | | `Channel` / `ARQChannel` / `SimpleChannel` port | 250 | 1.5 | | | `kcp` PyPI package integration | 100 | 0.5 | | | `NativeAppContext` (signaling, kill timers) | 350 | 2 | 10-min SIGHUP grace, SIGTERM escalation | | `ProxyInputOutput` (FD workaround over WS) | 300 | 2 | memfd_create, mkfifo via os.pipe | | libwayland event-loop integration | 80 | 1 | 5-line cffi shim for `wl_event_loop_get_fd` | | Multiprocessing scaffolding + FD passing | 200 | 1.5 | `multiprocessing.reduction.send_handle` | | GStreamer encoder thread + marshalling | 250 | 2 | `asyncio.run_coroutine_threadsafe` | | Reconnection & lifecycle | 150 | 1 | | | Tests (pytest + pytest-asyncio) | 600 | 3 | | | **TOTAL** | **~2,880** | **~17 days** | | ### Odoo 20 integration — custom addon | Component | Dev-days | Notes | |---|---|---| | Odoo addon skeleton (models + controllers + views + security) | 2 | compositor.app, compositor.session, compositor.audit.log | | JWT issuance + audit log model | 1.5 | HS256, 15-min expiry | | OWL 3 fullscreen client action + canvas + WS wiring | 2.5 | `ir.actions.client` with `target=fullscreen` | | Browser compositor library integration | 2 | `userShell.actions.initScene(canvasCreator)` | | Compositor proxy JWT middleware + WS handshake | 1.5 | | | Audit POSTback + Nginx + Docker Compose + GPU passthrough | 2 | | | Multi-tenant: per-DB proxy spawning + db claim scoping | 1.5 | | | Tests (HttpCase + TransactionCase) | 2 | | | Documentation + buffer | 3 | | | **TOTAL** | **~18 days** | | ### Sandboxing — NEW (Greenfield has none) | Component | Dev-days | Notes | |---|---|---| | bubblewrap integration | 2-3 | | | seccomp profile + compile | 1-2 | Docker default profile + CLONE_NEWUSER allow | | systemd-run cgroup wrapper | 1 | MemoryMax=1G, CPUQuota=200%, TasksMax=256 | | Wayland socket ownership fix | 0.5 | chmod 0660 + add sandbox group | | GPU passthrough testing | 1 | /dev/dri/renderD128 only | | Schema + docs | 1 | | | **v1 TOTAL** | **~7-9 days** | shippable, XWayland unsandboxed | | XWayland sandbox (v2) | +4-9 | | | veth + nftables networking (v2) | +2 | | | Per-tenant hierarchical cgroups (v2) | +2 | | | XDG Desktop Portal (FileChooser/Secret) (v2) | +3 | | ## Architecture at a glance ``` ┌──────────────────────────────────────────────────────────────────────────────────┐ │ BROWSER (Odoo 20 web client) │ │ │ │ ┌────────────────────────────────────────────────────────────────────────────┐ │ │ │ Odoo 20 OWL Shell addon (PROPRIETARY) │ │ │ │ - ir.actions.client target=fullscreen │ │ │ │ - owned by OWL component │ │ │ │ - Implements userShell.events.* callbacks │ │ │ │ - Calls userShell.actions.initScene, activateSurface, ... │ │ │ └────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ userShell.events + actions (clean-room spec) │ │ ▼ │ │ ┌────────────────────────────────────────────────────────────────────────────┐ │ │ │ Custom Browser Compositor Library (PROPRIETARY, clean-room TS) │ │ │ │ - Session, Compositor, Globals, Surface, View, Output, Seat, ... │ │ │ │ - render/ (WebGL2 + OffscreenCanvas + Web Workers) │ │ │ │ - transport/ (WebSocket + KCP + ReconnectingWebSocket) │ │ │ │ - roles/ (xdg-shell state machine, clean-room from spec) │ │ │ │ - desktop/, input/, data/, math/ │ │ │ │ - userapi/ (UserShellApi — the OWL shell integration seam) │ │ │ │ ~13,400 LOC, ~25 dev-weeks for MVP │ │ │ │ │ │ │ │ Reuses MIT libs as npm deps: │ │ │ │ - @gfld/common, @gfld/compositor-protocol, @gfld/client-protocol (MIT) │ │ │ │ - @gfld/compositor-wasm (pixman + xkbcommon WASM, MIT) │ │ │ │ - @gfld/xtsb (MIT, X11 — only if XWayland needed) │ │ │ │ - WebCodecs API (W3C standard, replaces GPL FFmpeg WASM) │ │ │ └────────────────────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────────────────┘ │ │ WebSocket (TCP) + KCP-over-WS │ 9 endpoint paths per session │ JWT (HS256) auth, 15-min expiry │ Same-origin via Nginx reverse proxy ▼ ┌──────────────────────────────────────────────────────────────────────────────────┐ │ PYTHON HOST (Linux server) │ │ │ │ ┌────────────────────────────────────────────────────────────────────────────┐ │ │ │ Nginx (TLS termination, BSD-2-Clause) │ │ │ │ / → Odoo :8069 (HTTP workers) │ │ │ │ /websocket → Odoo :8072 (gevent, Discuss/chat) │ │ │ │ /compositor/ → Compositor proxy :8081 (aiohttp WS, proxy_buffering off) │ │ │ └────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌────────────────────────────────────────────────────────────────────────────┐ │ │ │ Odoo 20 (LGPL-3.0, separate process — no contamination) │ │ │ │ - HTTP workers (preforked, N processes) │ │ │ │ - Controllers: /web/compositor/{token, apps, launch, terminate, audit} │ │ │ │ - Models: compositor.app, compositor.session, compositor.audit.log │ │ │ │ - JWT issuer (HS256 secret in ir.config_parameter) │ │ │ └────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ subprocess + IPC │ │ ▼ │ │ ┌────────────────────────────────────────────────────────────────────────────┐ │ │ │ Python Compositor Proxy (PROPRIETARY, ~2880 LOC) │ │ │ │ - aiohttp WebSocket server on :8081, 9 endpoints │ │ │ │ - asyncio + 5-line cffi shim for libwayland event loop │ │ │ │ - multiprocessing.Process per Odoo session (fork) │ │ │ │ - JWT validation (pyjwt) │ │ │ │ - pywayland (Apache-2.0) for Wayland protocol dispatch │ │ │ │ - python-xkbcommon (MIT) for keymap compilation │ │ │ │ - PyGObject+GStreamer (LGPL) for H.264/PNG encoding │ │ │ │ - Uses vaapih264enc / nvh264enc / openh264enc (NOT x264enc — GPL) │ │ │ │ - kcp (PyPI, MIT, Cython wrapper over ikcp.c) for ARQ channels │ │ │ │ - Vendored Greenfield C files (MIT) for dmabuf/egl/xwayland helpers │ │ │ │ - Greenfield's changes.diff applied to libwayland-server (MIT patches) │ │ │ │ - bubblewrap + seccomp + systemd-run --scope for app sandboxing │ │ │ └────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ WAYLAND_DISPLAY=wayland-N │ │ ▼ │ │ ┌────────────────────────────────────────────────────────────────────────────┐ │ │ │ Native Linux apps (gtk4-demo, xterm, kwrite, gnome-calculator, ...) │ │ │ │ - Spawned via: systemd-run --user --scope -p MemoryMax=1G -p CPUQuota=200% │ │ │ │ → bwrap --unshare-all --clearenv --seccomp --ro-bind /usr /usr │ │ │ │ --dev-bind /dev/dri/renderD128 /dev/dri/renderD128 │ │ │ │ --ro-bind $HOST_XDG/wayland-N /run/user//wayland-N │ │ │ │ --uid <100000-165535 per-app> --gid │ │ │ │ --new-session --die-with-parent │ │ │ │ /usr/bin/ │ │ │ │ - Filesystem: /usr ro-bind, minimal /etc whitelist, $HOME overlay │ │ │ │ - Network: --unshare-net by default (most apps don't need it) │ │ │ │ - GPU: /dev/dri/renderD128 only (kernel render-node access control) │ │ │ └────────────────────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────────────────┘ ``` ## What we GAIN over Greenfield 1. **Full proprietary control** — no AGPL contamination, ship as a closed-source Odoo addon. 2. **Modern browser stack** — WebGL2 (vs Greenfield's WebGL1), OffscreenCanvas + Web Workers (vs Greenfield's main-thread WebGL), WebCodecs with hardware decode enabled (Greenfield disables it with a FIXME). 3. **Sandboxing** — bubblewrap + seccomp + systemd-run + cgroups. Greenfield has zero sandboxing. 4. **Multi-tenancy** — per-Odoo-database proxy isolation, JWT-scoped sessions. Greenfield is single-user single-session. 5. **Odoo integration** — auth via Odoo session, audit log, app launcher from Odoo's app catalog. Greenfield has its own Preact shell. 6. **Adaptive bitrate** — consume the `EncoderFeedback` data Greenfield produces but doesn't use. 7. **Primary selection** — middle-click paste, which Greenfield lacks. 8. **Real touch input** — Greenfield has a 46-line stub that never fires. 9. **Modern codecs** — VP9/AV1 support via WebCodecs (Greenfield is H.264 only). 10. **Clean upgrade path** — no native Node addons (no `.node` files), no Node.js ABI lock-in, no CMake/Ninja build chain. ## What we LOSE vs Greenfield 1. **~25 dev-weeks of engineering** — the browser compositor is the biggest cost. Clean-room rewrite is mandatory for legal safety. 2. **Greenfield's battle-tested codebase** — we'd be writing fresh Wayland state machines, which is subtle (xdg-shell configure/ack round-trips, subsurface sync/desync cascades, popup positioner math). 3. **Time-to-market** — Greenfield works today; our MIT approach takes ~6-8 months to MVP. 4. **Greenfield's WASM app SDK** — the `sdk/` directory is for compiling native Wayland apps to WASM and running them in the browser. If you need this feature, it's a separate ~4-8 week project to port. ## Total cost summary (single engineer, sequential) | Phase | Weeks | Cumulative | |---|---|---| | Phase 0: Spike (validate libwayland ↔ asyncio) | 1 | 1 | | Phase 1: Browser compositor MVP (clean-room TS) | 25 | 26 | | Phase 2: Python proxy MVP (aiohttp + pywayland + GStreamer) | 3.5 | 29.5 | | Phase 3: Odoo integration (OWL shell + backend addon + JWT) | 3.5 | 33 | | Phase 4: Sandboxing v1 (bubblewrap + seccomp) | 1.5 | 34.5 | | Phase 5: Polish & testing | 3 | 37.5 | | **MVP TOTAL** | **~37.5 weeks** | (~9 months) | | Phase 6 (optional): XWayland + multi-tenant + portal | +9 | ~46.5 weeks | With 2 senior engineers in parallel (one on browser compositor, one on Python proxy + Odoo integration): | Phase | Weeks | Cumulative | |---|---|---| | Phase 0: Spike | 1 | 1 | | Phase 1+2 (parallel) | 25 (browser is critical path) | 26 | | Phase 3+4 (parallel) | 3.5 | 29.5 | | Phase 5: Polish & testing | 3 | 32.5 | | **MVP TOTAL** | **~32.5 weeks** | (~8 months) | | Phase 6 (optional): XWayland + multi-tenant + portal | +6 | ~38.5 weeks | See `../Greenfield-vs-MIT-Comparison.md` for the head-to-head comparison.