313 lines
22 KiB
Markdown
313 lines
22 KiB
Markdown
# 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.
|