Files

22 KiB

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                                      │  │
│  │  - <canvas t-ref="canvas"> 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 <memfd> --ro-bind /usr /usr   │  │
│  │      --dev-bind /dev/dri/renderD128 /dev/dri/renderD128                      │  │
│  │      --ro-bind $HOST_XDG/wayland-N /run/user/<UID>/wayland-N                │  │
│  │      --uid <100000-165535 per-app> --gid <same>                              │  │
│  │      --new-session --die-with-parent                                          │  │
│  │      /usr/bin/<app>                                                          │  │
│  │  - 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.