You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+8-4Lines changed: 8 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4
4
5
5
## What this is
6
6
7
-
libCEC is a cross-platform C++ library for controlling CEC-capable hardware (TVs, AV receivers, etc.) over HDMI, primarily via Pulse-Eight's USB-CEC adapter and SoC-native CEC on Linux/Raspberry Pi. It exposes C, C++, Python (via SWIG), .NET, and Node.js interfaces over the same core engine. The shared/static library output is named `cec` (`libcec.so` / `cec.dll`).
7
+
libCEC is a cross-platform C++ library for controlling CEC-capable hardware (TVs, AV receivers, etc.) over HDMI, primarily via Pulse-Eight's USB-CEC adapter and SoC-native CEC on Linux/Raspberry Pi. It exposes C, C++, Python (via SWIG), .NET, Node.js and Rust interfaces over the same core engine. The shared/static library output is named `cec` (`libcec.so` / `cec.dll`).
8
8
9
9
The Node.js binding **`libcec`** lives in `src/nodejs` in this repo. It is a **native N-API addon** (`node-addon-api`, built with `node-gyp`) that binds libCEC via the C API (`include/cecc.h`) — the same surface the .NET binding uses. `src/nodejs/src/addon.cc` is the C++ addon (an `EventEmitter`-based `CecAdapter`); libCEC fires its `ICECCallbacks` from its own worker thread, so each C trampoline copies its payload and re-enters JS via a `Napi::ThreadSafeFunction`. The `commandHandler`/`menuStateChanged` callbacks return "not handled" (0) synchronously — honouring a JS return would mean blocking libCEC's callback thread on the event loop and racing its 1000ms timeout. `src/nodejs/lib/` is the JS wrapper (enums + the EventEmitter surface).
10
10
@@ -14,6 +14,8 @@ The Node.js binding **`libcec`** lives in `src/nodejs` in this repo. It is a **n
14
14
15
15
The managed binding **`LibCecSharp`** lives in `src/dotnetlib` in this repo. It is a **pure C# assembly** (namespace `CecSharp`) that binds libCEC via P/Invoke over the C API (`include/cecc.h` → `LibCECC.cpp`), targets **net8.0**, and compiles with `dotnet build` (no MSVC `/clr`) — so unlike the old C++/CLI wrappers it works on Linux/macOS/RPi as well as Windows. It replaced two Windows-only C++/CLI wrappers (`LibCecSharp` for .NET Framework + `LibCecSharpCore` for net8.0), unifying them into one assembly. The `.NET` client apps (cec-tray, CecSharpTester) live in the `src/dotnet` git submodule (the `cec-dotnet` repo) and both reference this one binding; `cec-tray` is a Windows-only WinForms app (net8.0-windows), `CecSharpTester` is a cross-platform net8.0 console sample.
16
16
17
+
The Rust binding **`libcec`** lives in `src/rust`. It binds libCEC over the same C API, and like the .NET binding it mirrors `cectypes.h` by hand rather than generating anything — `src/rust/src/ffi.rs` is the raw surface, and `tests/layout.rs` asserts every struct size and field offset against numbers taken from a C compiler reading the real headers. **The crate has no dependencies at all**, deliberately: that is what lets `cargo build --offline` work, which is what lets cmake and the Debian package build it with no network and no vendored registry. Don't add one without a reason that outweighs that. The protocol enums (~290 values) are *generated* by `support/generate-rust-enums.py` from `cectypes.h` and checked in — re-run it after adding a value, it runs `rustfmt` itself. Two things it works around: CEC gives `UNREGISTERED` and `BROADCAST` the same value, so duplicates become associated constants aliasing the variant, and every enum has an `Other(i32)` catch-all with a total `from_raw`, because the bus carries whatever devices put on it. `Display` defers to libCEC's own `libcec_*_to_string` helpers so the names can't drift. Callbacks come two ways: implementing `CecCallbacks` runs on libCEC's worker thread and is the only way to *answer* `menu_state_changed`/`command_handler`, while `callbacks::channel()` gives an `mpsc::Receiver<CecEvent>` and is the sane default. libCEC keeps the addresses of the callback table and callback parameter, so both live in a pinned box whose `Drop` closes and destroys *before* the handler is dropped — `libcec_destroy` joins the worker thread, which is what makes that ordering sound. `open_first()` is a null port passed straight to `libcec_open`: `CLibCEC::Open` detects adapters itself when it isn't given one and opens the first that *opens*, skipping any another process holds, so the binding doesn't detect anything of its own.
18
+
17
19
## Submodules
18
20
19
21
The one remaining submodule, `src/dotnet` (the cec-dotnet .NET apps), is **only used by the Windows build** — init it there first:
@@ -44,6 +46,8 @@ Platform-native CEC backends are **off by default** and selected with cmake flag
44
46
45
47
The managed .NET binding is also a cmake option, **off by default** so a normal build never needs the .NET SDK: `-DENABLE_DOTNET_LIB=1` builds the pure-C# `LibCecSharp` binding via `dotnet build` (any platform with the SDK), and `-DENABLE_DOTNET_APPS=1` additionally builds the Windows-only .NET apps (cec-tray, CecSharpTester) and implies `ENABLE_DOTNET_LIB`. These are `dotnet build` custom targets in the top-level `CMakeLists.txt`; they never enter the native build graph. `DOTNET_ARCH` (default x64 on Windows, AnyCPU elsewhere) sets the managed `-p:Platform`.
46
48
49
+
The Rust binding is a cmake option too, **off by default**: `-DENABLE_RUST_LIB=1` builds the crate with `cargo build --offline` and installs its *sources* under `share/cargo/registry/libcec-<version>/` (Rust has no stable ABI, so a compiled rlib would only be usable by the compiler that built it; this is the layout `dh-cargo` produces and a cargo `directory` source consumes). It builds the **examples**, not just the library — an rlib is never linked, so `cargo build` alone would not prove the bindings resolve against libCEC. Unlike the Node option, the Debian package uses this one directly (`debian/rules`), because zero crate dependencies means no network.
50
+
47
51
The Node.js binding is likewise a cmake option, **off by default**: `-DENABLE_NODE_LIB=1` builds the native addon via `npm install` (runs `node-gyp`, compiling `src/nodejs` against a `pkg-config`-discoverable libCEC) and installs it as a global node module under `lib/node_modules/libcec`. Like the .NET targets it's a custom target, never in the native build graph. The Debian `node-libcec` package does **not** use this option (npm needs network); `debian/rules` builds the addon with `node-gyp` against the staged libCEC via `PKG_CONFIG_SYSROOT_DIR` instead.
`project/libcec.sln` opens the C# `LibCecSharp` binding (`src/dotnetlib/LibCecSharp.csproj`, an SDK-style project) for development in Visual Studio; the .NET apps solution is `src/dotnet/project/cec-dotnet.sln` (cec-tray + CecSharpTester). The actual build goes through cmake's `ENABLE_DOTNET_*` targets — all SDK-based, no `/clr`.
63
67
64
68
### Debian / Ubuntu packaging
65
-
`debian/` builds the `.deb` set (`dpkg-buildpackage`; see `docs/README.debian.md`). The runtime package is **`libcec8`** — named after the SONAME (`= LIBCEC_VERSION_MAJOR`), so it is renamed on every major bump and `Breaks`/`Replaces`/`Provides` the older `libcec4`-`libcec7` names; `libcec8-dev` likewise supersedes the old `-dev` names. The other packages are `cec-utils` (cec-client + cecc-client), `python-libcec`, `libcec-dotnet` (the managed binding + its NuGet package, built by passing `-DENABLE_DOTNET_LIB=1`), and a `libcec` meta package. `debian/rules` also enables the Linux/Exynos/AOCEC backends and reproducible-build flags. `debian/changelog.in` (not `changelog`) is the source — `#DIST#` is substituted per distribution at build time.
69
+
`debian/` builds the `.deb` set (`dpkg-buildpackage`; see `docs/README.debian.md`). The Rust package is `librust-libcec-dev` (`Architecture: all`, crate sources only). The runtime package is **`libcec8`** — named after the SONAME (`= LIBCEC_VERSION_MAJOR`), so it is renamed on every major bump and `Breaks`/`Replaces`/`Provides` the older `libcec4`-`libcec7` names; `libcec8-dev` likewise supersedes the old `-dev` names. The other packages are `cec-utils` (cec-client + cecc-client), `python-libcec`, `libcec-dotnet` (the managed binding + its NuGet package, built by passing `-DENABLE_DOTNET_LIB=1`), and a `libcec` meta package. `debian/rules` also enables the Linux/Exynos/AOCEC backends and reproducible-build flags. `debian/changelog.in` (not `changelog`) is the source — `#DIST#` is substituted per distribution at build time.
66
70
67
71
### Releasing
68
-
`support/release.py` cuts a release in one non-interactive run: it merges `master` into `release`, annotates the tag on the **merge commit**, pushes both, waits for Jenkins to build and sign the tag, downloads the signed artefacts and publishes the GitHub release with them. It needs `JENKINS_URL`/`JENKINS_USER`/`JENKINS_TOKEN` in the environment (`--insecure` for a self-signed controller certificate) and an authenticated `gh`; its only arguments are the tag and a markdown notes file. It refuses to start on a dirty tree, a tag that already exists, a tag that disagrees with `LIBCEC_VERSION_*`, or an existing GitHub release, and refuses to publish an installer whose Authenticode signature does not verify. Preparing `master` is still done by hand beforehand: `CMakeLists.txt` is the source of truth for the version, and the files that repeat it have to be bumped along with it — the new `debian/changelog.in` stanza, `src/nodejs/package.json`, and `src/dotnetlib/LibCecSharp.csproj` (generated from its `.in`, but tracked so Visual Studio can open it without a cmake run, so the tracked copy goes stale on its own). `release.py`'s `SATELLITE_VERSIONS` table lists them and the release stops if any disagrees — 8.1.4 shipped an npm module calling itself 8.1.0 before that check existed. **A new binding that carries its own version file adds a line to that table.**
72
+
`support/release.py` cuts a release in one non-interactive run: it merges `master` into `release`, annotates the tag on the **merge commit**, pushes both, waits for Jenkins to build and sign the tag, downloads the signed artefacts and publishes the GitHub release with them. It needs `JENKINS_URL`/`JENKINS_USER`/`JENKINS_TOKEN` in the environment (`--insecure` for a self-signed controller certificate) and an authenticated `gh`; its only arguments are the tag and a markdown notes file. It refuses to start on a dirty tree, a tag that already exists, a tag that disagrees with `LIBCEC_VERSION_*`, or an existing GitHub release, and refuses to publish an installer whose Authenticode signature does not verify. Preparing `master` is still done by hand beforehand: `CMakeLists.txt` is the source of truth for the version, and the files that repeat it have to be bumped along with it — the new `debian/changelog.in` stanza, `src/nodejs/package.json`, and `src/dotnetlib/LibCecSharp.csproj` (generated from its `.in`, but tracked so Visual Studio can open it without a cmake run, so the tracked copy goes stale on its own). `release.py`'s `SATELLITE_VERSIONS` table lists them and the release stops if any disagrees — 8.1.4 shipped an npm module calling itself 8.1.0 before that check existed. **A new binding that carries its own version file adds a line to that table.** Publishing a binding to its language's registry (npm, NuGet, crates.io) is *not* automated and is a deliberate step: those versions are permanent and can only be yanked, never replaced.
69
73
70
74
### API documentation
71
-
`docs/api/` holds the per-binding API reference, one best-of-breed generator each: **Doxygen** for C/C++ (`docs/api/doxygen/`, main page in `mainpage.md`), **DocFX** for .NET (`docs/api/dotnet/`, compiles a docs-only `docs.csproj` that globs the same `cs/` sources), **TypeDoc** for Node.js (`docs/api/nodejs/`, renders the hand-authored `src/nodejs/index.d.ts` — which also ships to consumers via `package.json` `types`), and **Sphinx** for Python (`docs/api/python/`, autodoc over a `swig -doxygen`-generated `cec.py` with the native `_cec` extension mocked, so no libCEC compile is needed). A landing page (`docs/api/landing/`) links the four; `docs/api/assets/` holds the shared Pulse-Eight logo. `.github/workflows/docs.yml` builds all four and deploys to GitHub Pages — **published at https://pulse-eight.github.io/libcec/**. Generated output is git-ignored; see `docs/api/README.md` for local build steps. The welcome pages carry the install+usage guide for each binding, so keep them current when an API surface changes.
75
+
`docs/api/` holds the per-binding API reference, one best-of-breed generator each: **Doxygen** for C/C++ (`docs/api/doxygen/`, main page in `mainpage.md`), **DocFX** for .NET (`docs/api/dotnet/`, compiles a docs-only `docs.csproj` that globs the same `cs/` sources), **TypeDoc** for Node.js (`docs/api/nodejs/`, renders the hand-authored `src/nodejs/index.d.ts` — which also ships to consumers via `package.json` `types`), **Sphinx** for Python (`docs/api/python/`, autodoc over a `swig -doxygen`-generated `cec.py` with the native `_cec` extension mocked, so no libCEC compile is needed), and **rustdoc** for Rust (no config — the crate's own doc comments are the input; `docs/api/rust/index.html` is only a redirect, since rustdoc writes no root index for a single crate, and CI runs it with `-D warnings`). A landing page (`docs/api/landing/`) links the five; `docs/api/assets/` holds the shared Pulse-Eight logo. `.github/workflows/docs.yml` builds all four and deploys to GitHub Pages — **published at https://pulse-eight.github.io/libcec/**. Generated output is git-ignored; see `docs/api/README.md` for local build steps. The welcome pages carry the install+usage guide for each binding, so keep them current when an API surface changes.
72
76
73
77
### Tests
74
78
There is no automated test suite. Verification is manual via the example clients run against real CEC hardware:
0 commit comments