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
- Full proprietary control — no AGPL contamination, ship as a closed-source
Odoo addon.
- 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).
- Sandboxing — bubblewrap + seccomp + systemd-run + cgroups. Greenfield
has zero sandboxing.
- Multi-tenancy — per-Odoo-database proxy isolation, JWT-scoped sessions.
Greenfield is single-user single-session.
- Odoo integration — auth via Odoo session, audit log, app launcher from
Odoo's app catalog. Greenfield has its own Preact shell.
- Adaptive bitrate — consume the
EncoderFeedback data Greenfield produces
but doesn't use.
- Primary selection — middle-click paste, which Greenfield lacks.
- Real touch input — Greenfield has a 46-line stub that never fires.
- Modern codecs — VP9/AV1 support via WebCodecs (Greenfield is H.264 only).
- 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
- ~25 dev-weeks of engineering — the browser compositor is the
biggest cost. Clean-room rewrite is mandatory for legal safety.
- 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).
- Time-to-market — Greenfield works today; our MIT approach takes
~6-8 months to MVP.
- 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.