Skip to main content

Remote Frame Buffer

Project description

pdum.rfb — Remote Frame Buffer

CI Coverage Documentation

PyPI Python 3.14+ License: MIT Code style: ruff

Render a framebuffer in Python, view and interact with it in the browser. pdum.rfb streams a server-rendered framebuffer to a browser over a WebSocket and sends pointer/keyboard/resize events back. It targets scientific and interactive visualization across the whole cadence range — from sparse, on-demand scenes (render only when state changes) to high-frame-rate interactive streaming (low-latency H.264/WebCodecs). You own the loop and pick the cadence; the library never imposes a fixed game-engine tick. It is not a generic VNC clone.

Coming from jupyter_rfb? Same idea — render in Python, view in the browser, events flow back, same renderview event vocabulary — so it slots under rendercanvas / pygfx / fastplotlib. What's different:

  • Not tied to Jupyter. Frames travel over a plain WebSocket, not ipywidgets/kernel comms — the same server drives a standalone web page, a desktop webview, or a headless box, no notebook required.
  • High frame rates. Alongside the per-frame image path (every frame a keyframe, à la jupyter_rfb), a low-latency H.264/WebCodecs path with per-client backpressure and keyframe policy streams continuous, interactive framerates — not just occasional redraws.
  • Zero-copy on the GPU. When you render on CUDA, frames can go straight to NVENC (CUDA NV12 → H.264) with no host round-trip — see GPU zero-copy.

The repo ships two halves:

  • a Python server — this package, habemus-papadum-rfb (import pdum.rfb), Python 3.14+, UV-managed;
  • a browser client@habemus-papadum/rfb-widgets, a TypeScript package whose decoding runs entirely in a Web Worker (it owns the WebSocket, the decoder, and a transferred OffscreenCanvas).

A sibling native package, habemus-papadum-nvenc (import pdum.nvenc, under packages/nvenc/), provides an optional PyAV-free GPU H.264 encoder.

📖 Full documentation: https://habemus-papadum.github.io/pdum_rfb/

How it works

The public API is push: you own your loop and publish frames into a shared Display; the library fans each frame out to every connected viewer and lets you drain input from all of them in one place.

import asyncio
import pdum.rfb as rfb

async def main():
    display = await rfb.serve(1280, 720, port=8765)   # WS server starts in the background
    state = initial_state()
    try:
        while running(state):
            for ev in display.poll_events():          # input from every viewer
                state = update(state, ev)
            display.publish(render(state))            # sync, latest-wins, fans out to all viewers
            await asyncio.sleep(1 / 30)               # or on-demand — you own the cadence
    finally:
        await display.aclose()

asyncio.run(main())
import { RemoteFramebufferView } from "@habemus-papadum/rfb-widgets";
const view = new RemoteFramebufferView(document.getElementById("stage")!, {
  url: "ws://localhost:8765",
});
// later: view.dispose();

Each connecting browser negotiates the best shared transport: an image path (JPEG/PNG/WebP, every frame a keyframe; dependency-light) or an H.264 path (Annex B for the browser's WebCodecs decoder). For GPU-rendered scenes, three hardware NVENC routes are available — see the Installation guide.

Installation

pip install habemus-papadum-rfb              # image path (numpy, pillow, websockets)
pip install 'habemus-papadum-rfb[h264]'      # + CPU/software H.264 (PyAV/libx264)
pip install 'habemus-papadum-rfb[gpu-nvenc-sdk]'   # + GPU H.264 (NVIDIA, Linux) — fastest
pip install 'habemus-papadum-rfb[anywidget]'       # + Jupyter/marimo notebook widget

import pdum.rfb works without any extra. Not sure what your machine supports? pip install 'habemus-papadum-rfb[cli]' then pdum-rfb doctor. The full matrix (CPU vs the three GPU routes, platform limits) is in the Installation guide.

Developing

The repo is a uv workspace (root habemus-papadum-rfb + packages/*) with the browser client as a self-contained pnpm project under widgets/. The layout, the uv/pnpm conventions, and the CI are documented in Repository & Development.

Prerequisites

Install these yourself first — setup.sh detects them and tells you how to install any that are missing, but it never installs them for you:

  • uvcurl -LsSf https://astral.sh/uv/install.sh | sh
  • Node.js 20+ and pnpm (for the browser client / e2e). The repo pins the tested LTS in .nvmrc (Node 22, which CI uses) — nvm use / fnm use picks it up. corepack enable (ships with Node) or npm i -g pnpm provides pnpm. Optional if you only work on the Python side. setup.sh refuses to set up the browser client on Node < 20.

Bootstrap (all platforms)

git clone https://github.com/habemus-papadum/pdum_rfb.git
cd pdum_rfb
./scripts/setup.sh        # idempotent — rerun after pulling dependency changes

One command sets up everything, the same way on macOS / Linux / Linux+GPU:

  • Pythonuv sync --frozen (the committed uv.lock is authoritative). On a Linux box with an NVIDIA GPU and a CUDA toolkit it auto-adds the native NVENC SDK encoder — see Per-platform notes for the RFB_GPU knob.
  • Browser clientpnpm install --frozen-lockfile plus the Playwright Chromium download used by the e2e suite (skipped, with a hint, if Node/pnpm are absent).
  • pre-commit hooks.

Per-platform notes

  • Linux without a GPU (and CI's default) — the bootstrap above is everything. The dev group already includes PyAV, so the image and CPU-H.264 paths and all their tests work. GPU tests detect no device and skip.

  • Linux with an NVIDIA GPUsetup.sh detects the GPU + CUDA toolkit and builds the PyAV-free NVENC SDK encoder (pdum.nvenc) as an editable install automatically. Override with the RFB_GPU env var:

    RFB_GPU=auto  ./scripts/setup.sh   # default: build it iff Linux + GPU + CUDA toolkit present
    RFB_GPU=force ./scripts/setup.sh   # build even if the CUDA major ≠ 13 (then swap CuPy yourself)
    RFB_GPU=0     ./scripts/setup.sh   # CPU paths only
    

    The gpu-nvenc-sdk extra pins cupy-cuda13x; on a CUDA-12 toolkit use RFB_GPU=force and swap to cupy-cuda12x. To confirm what lit up:

    uv run --group gpu-dev pdum-rfb doctor     # which encode paths are available
    

    For the PyAV-18 zero-copy route specifically, ./scripts/install-gpu.sh builds a CUDA-enabled PyAV. See the GPU zero-copy guide.

  • macOS — the image and CPU-H.264 paths work (PyAV publishes arm64 wheels). The NVENC/GPU paths are NVIDIA/Linux-only and are simply unavailable; everything else, including the full headless test suite for the CPU paths, runs normally.

Common commands

uv run pytest                          # Python tests
uv run ruff check . && uv run ruff format .
uv run python -m pdum.rfb.server --pattern bouncing_box --port 8765   # demo server
uv run mkdocs serve                    # docs at http://localhost:8000

pnpm -C widgets typecheck              # browser client: types
pnpm -C widgets test                   #                 Vitest unit tests
pnpm -C widgets e2e                    #                 Playwright e2e (boots the Python server)
pnpm -C widgets dev                    #                 demo at http://localhost:5173

Releasing

Maintainers only. Releasing publishes to PyPI/npm, pushes tags, and creates public GitHub releases. Version numbers are human-managed — don't hand-edit them.

./scripts/release.sh <patch|minor|major> bumps the version across all four version files in lockstep (pyproject.toml, src/pdum/rfb/__init__.py, widgets/package.json, packages/nvenc/pyproject.toml), then — via an interactive step selector — commits, tags, pushes, and publishes all three packages:

  • PyPIscripts/publish.sh publishes both habemus-papadum-rfb (hatch) and the native habemus-papadum-nvenc wheels.
  • npm@habemus-papadum/rfb-widgets.
  • GitHub Release — which triggers the docs site to redeploy.

Publishing is never done from CI. It's non-interactive when a git-ignored .env at the repo root supplies credentials (loaded by the release/publish scripts; pre-set env vars win):

# .env  (git-ignored — never commit)
HATCH_INDEX_USER=__token__
HATCH_INDEX_AUTH=pypi-…     # PyPI token (hatch does NOT read ~/.pypirc)
NPM_TOKEN=npm_…             # npm *Automation* token (bypasses 2FA)

release.sh materializes NPM_TOKEN into a transient, outside-the-repo .npmrc just for the pnpm publish call — there is no committed or persistent .npmrc, so dev checkouts stay auth-config-free. Full mechanics and the CI overview are in Repository & Development.

License

MIT License — see LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

habemus_papadum_rfb-0.1.0.tar.gz (763.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

habemus_papadum_rfb-0.1.0-py3-none-any.whl (201.5 kB view details)

Uploaded Python 3

File details

Details for the file habemus_papadum_rfb-0.1.0.tar.gz.

File metadata

  • Download URL: habemus_papadum_rfb-0.1.0.tar.gz
  • Upload date:
  • Size: 763.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: Hatch/1.17.0 {"ci":null,"cpu":"arm64","distro":{"name":"macOS","version":"26.5.1"},"implementation":{"name":"CPython","version":"3.14.0"},"installer":{"name":"hatch","version":"1.17.0"},"openssl_version":"OpenSSL 3.6.2 7 Apr 2026","python":"3.14.0","system":{"name":"Darwin","release":"25.5.0"}} HTTPX2/2.5.0

File hashes

Hashes for habemus_papadum_rfb-0.1.0.tar.gz
Algorithm Hash digest
SHA256 dfd7a6113f7de820b1183ce88211f14bc439773b6146889e0e383f2b4b4c0eab
MD5 cc620d2192a952c28014e100c3433c56
BLAKE2b-256 07e6ba347375f5252f3f1b7ded5d03d28fbaee21a4f3d9d24c6aec89ff159287

See more details on using hashes here.

File details

Details for the file habemus_papadum_rfb-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: habemus_papadum_rfb-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 201.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: Hatch/1.17.0 {"ci":null,"cpu":"arm64","distro":{"name":"macOS","version":"26.5.1"},"implementation":{"name":"CPython","version":"3.14.0"},"installer":{"name":"hatch","version":"1.17.0"},"openssl_version":"OpenSSL 3.6.2 7 Apr 2026","python":"3.14.0","system":{"name":"Darwin","release":"25.5.0"}} HTTPX2/2.5.0

File hashes

Hashes for habemus_papadum_rfb-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 813ae70e8901499c575628e43d946e4fc0b17fef3e18cd0ff6b016d2e2491256
MD5 6fa5d8f09fcb63cfe063ee2704bc7b82
BLAKE2b-256 03f97e4f9e543eaa06f816cad7d60679e5f40bd40d60b5900c4c8799c601f678

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page