mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
# Building CEF with Proprietary Codecs
|
||||
|
||||
Tracks issue #1223 — vendored CEF lacks H.264 / AAC support so Google Meet's
|
||||
dynamic (video) virtual backgrounds, embedded YouTube/Vimeo previews, and
|
||||
any HTML5 `<video>` source pulling H.264-in-MP4 fail with
|
||||
`MEDIA_ERR_SRC_NOT_SUPPORTED: PipelineStatus::DEMUXER_ERROR_NO_SUPPORTED_STREAMS:
|
||||
FFmpegDemuxer: no supported streams`. Empirical confirmation of the codec
|
||||
absence is in #1223 and in [`feedback_cef_runtime_gaps.md`](https://github.com/tinyhumansai/openhuman/issues/1223#issuecomment-4379209818)
|
||||
gap #3.
|
||||
|
||||
The Spotify CDN (`cef-builds.spotifycdn.com`) — which `download-cef` and
|
||||
all other public CEF wrappers default to — ships **only** open-source
|
||||
codecs. Every flavor (`standard`, `minimal`, `client`, `tools`, `*_symbols`)
|
||||
is built with `proprietary_codecs = false`. To get H.264 / AAC support
|
||||
into the embedded webview we have to compile CEF ourselves with
|
||||
Chrome-branded FFmpeg and host the resulting binary somewhere our build
|
||||
script can fetch it from.
|
||||
|
||||
This directory is the build infrastructure for that: scripts that drive
|
||||
the upstream `automate-git.py` toolchain, a local install helper that
|
||||
drops the result into `CEF_PATH` so `cargo build` picks it up, and the
|
||||
license posture / hosting documentation.
|
||||
|
||||
> **The actual built binary is NOT committed to this repo and never will
|
||||
> be.** It is multiple gigabytes and carries license obligations (see
|
||||
> below). Hosting + distribution is a separate operational concern.
|
||||
|
||||
---
|
||||
|
||||
## License posture (READ BEFORE BUILDING)
|
||||
|
||||
H.264 / AVC carries patent obligations under the
|
||||
[MPEG-LA AVC Patent Portfolio License](https://www.mpegla.com/programs/avc-h-264/).
|
||||
Bundling an H.264 decoder into a redistributed application can require
|
||||
royalty payments depending on:
|
||||
|
||||
- distribution model (free vs paid),
|
||||
- annual end-user count,
|
||||
- whether the decoder is hardware-accelerated by the OS (some royalty
|
||||
carve-outs apply for "system supplied" decoders),
|
||||
- jurisdiction.
|
||||
|
||||
Browsers like Firefox sidestep this by downloading Cisco's OpenH264 binary
|
||||
plugin at runtime — Cisco pays the royalties on their users' behalf. CEF
|
||||
does not currently ship that plugin path.
|
||||
|
||||
**Before running this build, get sign-off from legal / business** on:
|
||||
|
||||
1. Whether the AVC license fee is in budget for OpenHuman's distribution
|
||||
channels (desktop installer, GitHub releases, app stores).
|
||||
2. Whether the AAC patent pool (separate licensor) is also in scope —
|
||||
AAC is bundled with H.264 in the same `proprietary_codecs = true`
|
||||
build flag, so you cannot have one without the other.
|
||||
3. Whether HEVC / H.265 should also be enabled (separate flag,
|
||||
`enable_hevc_parser_and_hw_decoder = true`, which has its own MPEG-LA
|
||||
pool).
|
||||
|
||||
If the answer to (1) or (2) is no, **stop here**. The honest fallback is
|
||||
to surface "dynamic backgrounds not supported" in the Effects picker UI
|
||||
(see #1223 path D) rather than ship without a license.
|
||||
|
||||
---
|
||||
|
||||
## Build inputs
|
||||
|
||||
| Variable | Value (CEF 146 line) |
|
||||
|---|---|
|
||||
| Target CEF version | `146.0.9+g3ca6a87+chromium-146.0.7680.165` (matches `cef = "=146.4.1"` in `app/src-tauri/Cargo.toml`) |
|
||||
| Chromium branch | `7680` |
|
||||
| GN args added | `proprietary_codecs=true ffmpeg_branding="Chrome"` |
|
||||
| Required GN args (already implied) | `is_official_build=true` (release builds only) |
|
||||
| Optional HEVC extension | `enable_hevc_parser_and_hw_decoder=true` (separate license) |
|
||||
| Build platforms | macOS arm64 + x86_64, Linux arm64 + x86_64, Windows x86_64 |
|
||||
| Disk required | ~150 GB per platform (Chromium source + build cache) |
|
||||
| Wall-clock | ~2-4 hours per platform on M2/M3 Mac, longer on Linux/Windows |
|
||||
| Output artifact | `cef_binary_<ver>_<platform>_minimal.tar.bz2` |
|
||||
|
||||
The `minimal` flavor is what `download-cef` already targets (matches
|
||||
`pub fn minimal()` selection in `download_cef::CefVersion`). Skipping
|
||||
the `standard` flavor saves ~200 MB per artifact and the sample apps
|
||||
aren't shipped to users.
|
||||
|
||||
---
|
||||
|
||||
## Build host requirements (per upstream CEF docs)
|
||||
|
||||
Per [CEF Automated Build Setup](https://bitbucket.org/chromiumembedded/cef/wiki/AutomatedBuildSetup.md):
|
||||
|
||||
- **macOS**: Xcode + macOS SDK matching the target Chromium milestone.
|
||||
- **Linux**: Ubuntu 22.04 LTS recommended; needs `clang`, `lld`,
|
||||
`libstdc++-12-dev`, plus the chromium `install-build-deps.sh` package
|
||||
set.
|
||||
- **Windows**: Visual Studio 2022 with the C++ workload, Windows 11 SDK.
|
||||
|
||||
All platforms: Python 3, Git, `depot_tools` (the script will pull a
|
||||
fresh copy if `--depot-tools-dir` doesn't exist).
|
||||
|
||||
---
|
||||
|
||||
## Quick start (single platform, local dev)
|
||||
|
||||
> **Prerequisites:** the build-host requirements above, plus the legal
|
||||
> sign-off documented in the license-posture section.
|
||||
|
||||
```bash
|
||||
# 1. Run the build (2-4 hours on M2/M3 Mac).
|
||||
# Output lands at $CEF_BUILD_DIR/chromium/src/cef/binary_distrib/
|
||||
./scripts/cef-with-codecs/build-cef-with-codecs.sh
|
||||
|
||||
# 2. Extract the resulting tarball to the cache that tauri-cef expects
|
||||
# so cargo build picks it up via the existing CEF_PATH wiring.
|
||||
./scripts/cef-with-codecs/install-local.sh
|
||||
|
||||
# 3. Verify the codec gates inside dev:app:
|
||||
pnpm dev:app
|
||||
# In a second terminal once a webview is loaded:
|
||||
node app/src-tauri/scripts/diagnose-cef-runtime.mjs probe # path may vary
|
||||
# Expect h264_baseline / h264_main / h264_high / aac_lc → true
|
||||
```
|
||||
|
||||
If `h264_baseline` returns `true` after step 3, the codec build is
|
||||
correctly installed. Re-run #1053 Phase B smoke (Gmeet → Effects → pick
|
||||
a dynamic / video background) to confirm the original symptom is gone.
|
||||
|
||||
---
|
||||
|
||||
## CI / shared distribution (out of scope for this PR)
|
||||
|
||||
The build script alone is enough for an individual developer to validate
|
||||
the fix end-to-end. To ship the binary to all developers + release builds
|
||||
without each machine needing a 4-hour compile:
|
||||
|
||||
1. Run the build on a powerful CI runner (GitHub Actions self-hosted, or
|
||||
a beefy on-prem box).
|
||||
2. Upload the resulting `cef_binary_*.tar.bz2` to a private CDN
|
||||
(`s3://openhuman-cef-builds/<version>/<platform>/...` or equivalent).
|
||||
3. Set `CEF_DOWNLOAD_URL` in `scripts/load-dotenv.sh` (or as a
|
||||
per-developer env override) to point at that CDN.
|
||||
4. The vendored `download-cef` crate will fetch from the new URL on
|
||||
first build, just like it currently does from Spotify CDN.
|
||||
|
||||
Tracking this hosting work as a follow-up issue once the legal sign-off
|
||||
comes back. The build script in this PR is the upstream half of that
|
||||
pipeline.
|
||||
|
||||
---
|
||||
|
||||
## What this PR does not do
|
||||
|
||||
- **Does not compile any binary.** The build is too long + too disk-heavy
|
||||
to run in CI on every PR. Maintainers run the script offline.
|
||||
- **Does not host any binary.** That belongs to the CDN follow-up above.
|
||||
- **Does not flip any default in `download-cef`.** Spotify CDN remains the
|
||||
default until the legal review + private CDN are in place.
|
||||
- **Does not enable HEVC.** That's a separate license pool; revisit if
|
||||
there's a concrete user-visible feature blocked on HEVC.
|
||||
- **Does not change vendored `tauri-cef`.** No submodule pin bump, no
|
||||
source-tree edits — the upstream crate already handles `CEF_PATH` /
|
||||
`CEF_DOWNLOAD_URL` overrides.
|
||||
|
||||
The only files this PR touches are this README, the two helper scripts,
|
||||
and the issue tracker (#1223 cross-link).
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- Issue #1223 — bug: dynamic Gmeet backgrounds fail on H.264 demux.
|
||||
- Issue #1053 — parent: Gmeet bg effects (this is the codec follow-up).
|
||||
- PR #1222 — surfaced + diagnosed the codec gap during gmeet routing-
|
||||
reliability work; harness `scripts/diagnose-cef-runtime.mjs` added there.
|
||||
- Memory `feedback_cef_runtime_gaps.md` — gap #3 reclassified, codec gap
|
||||
documented with full diagnostic procedure.
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build CEF 146.0.9 (chromium 146.0.7680.165) with proprietary codecs
|
||||
# (H.264, AAC) enabled. Output is a tarball compatible with tauri-cef's
|
||||
# `download-cef` extractor — drop it into `$CEF_PATH/<version>/<platform>/`
|
||||
# (or run the install-local.sh sibling helper) and `cargo build` picks
|
||||
# it up via the existing rerun-if-env-changed=CEF_PATH wiring in
|
||||
# `cef-dll-sys/build.rs`.
|
||||
#
|
||||
# License: H.264 / AAC carry MPEG-LA royalty obligations. Read
|
||||
# scripts/cef-with-codecs/README.md and get legal sign-off before running.
|
||||
#
|
||||
# Tracks #1223. Reuses the upstream `automate-git.py` toolchain rather
|
||||
# than wrapping cef_create_projects.sh / `gn gen` ourselves so the build
|
||||
# matches what Spotify CDN ships in everything except the codec flags.
|
||||
#
|
||||
# Usage:
|
||||
# ./build-cef-with-codecs.sh # build for the host platform
|
||||
# CEF_BUILD_DIR=/Volumes/Big/cef ./build-cef-with-codecs.sh
|
||||
# CEF_BRANCH=7704 ./build-cef-with-codecs.sh # newer chromium milestone
|
||||
#
|
||||
# Outputs (per the automate-git.py contract):
|
||||
# $CEF_BUILD_DIR/chromium/src/cef/binary_distrib/cef_binary_<ver>_<plat>_minimal/
|
||||
# $CEF_BUILD_DIR/chromium/src/cef/binary_distrib/cef_binary_<ver>_<plat>_minimal.tar.bz2
|
||||
#
|
||||
# Wall-clock: ~2-4 hours on M2/M3, longer on Linux/Windows.
|
||||
# Disk: ~150 GB peak.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# --- Build inputs ---------------------------------------------------
|
||||
|
||||
# Match the cef crate version pinned in `app/src-tauri/Cargo.toml`
|
||||
# (`cef = "=146.4.1"`), which `download_cef::default_version` maps to
|
||||
# the Chromium 146.0.7680.165 line. Bump in lock-step with the crate
|
||||
# version when upgrading.
|
||||
CEF_BRANCH="${CEF_BRANCH:-7680}"
|
||||
|
||||
# Where to put the ~150 GB Chromium checkout + build cache. Default to
|
||||
# the user's home; override to an external disk if home is small.
|
||||
CEF_BUILD_DIR="${CEF_BUILD_DIR:-$HOME/cef-build}"
|
||||
|
||||
# Target platform. The script auto-detects from the host but you can
|
||||
# override (e.g. cross-build x86_64 on an arm64 Mac via macOS universal).
|
||||
ARCH="${ARCH:-$(uname -m)}"
|
||||
case "$(uname -s)" in
|
||||
Darwin)
|
||||
case "$ARCH" in
|
||||
arm64|aarch64) PLATFORM_FLAG="--arm64-build" ;;
|
||||
x86_64) PLATFORM_FLAG="--x64-build" ;;
|
||||
*) echo "Unsupported macOS arch: $ARCH" >&2; exit 1 ;;
|
||||
esac
|
||||
;;
|
||||
Linux)
|
||||
case "$ARCH" in
|
||||
aarch64|arm64) PLATFORM_FLAG="--arm64-build" ;;
|
||||
x86_64) PLATFORM_FLAG="--x64-build" ;;
|
||||
*) echo "Unsupported Linux arch: $ARCH" >&2; exit 1 ;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported host: $(uname -s). Build on macOS or Linux; use the Windows VS shell separately." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Prerequisite check ---------------------------------------------
|
||||
|
||||
require() {
|
||||
command -v "$1" >/dev/null 2>&1 || {
|
||||
echo "[cef-build] missing dependency: $1" >&2
|
||||
echo "[cef-build] see scripts/cef-with-codecs/README.md → 'Build host requirements'" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
require git
|
||||
require python3
|
||||
|
||||
# --- Set up depot_tools + CEF source --------------------------------
|
||||
|
||||
mkdir -p "$CEF_BUILD_DIR"
|
||||
cd "$CEF_BUILD_DIR"
|
||||
|
||||
if [[ ! -d depot_tools ]]; then
|
||||
echo "[cef-build] cloning depot_tools"
|
||||
git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git
|
||||
fi
|
||||
export PATH="$CEF_BUILD_DIR/depot_tools:$PATH"
|
||||
|
||||
if [[ ! -d cef ]]; then
|
||||
echo "[cef-build] cloning cef wrapper"
|
||||
git clone https://bitbucket.org/chromiumembedded/cef.git cef
|
||||
fi
|
||||
|
||||
# --- Run the build --------------------------------------------------
|
||||
|
||||
# `automate-git.py` orchestrates: chromium fetch / sync, depot_tools
|
||||
# bootstrap, GN gen with the merged custom + default args, and
|
||||
# cef_create_projects.sh + ninja invocation. The CEF docs are the
|
||||
# authoritative source for these flags:
|
||||
# https://bitbucket.org/chromiumembedded/cef/wiki/AutomatedBuildSetup.md
|
||||
|
||||
# CEF takes its build flags as a colon-separated list passed via
|
||||
# `--build-target` GN_DEFINES env, NOT as `--build-arg`. The
|
||||
# proprietary_codecs + ffmpeg_branding pair is what unlocks H.264 / AAC
|
||||
# in the resulting libcef.
|
||||
export GN_DEFINES='proprietary_codecs=true ffmpeg_branding=Chrome is_official_build=true'
|
||||
|
||||
echo "[cef-build] starting automate-git.py — this will take 2-4 hours and consume ~150 GB"
|
||||
echo "[cef-build] branch: $CEF_BRANCH (chromium 146.0.7680.165 line)"
|
||||
echo "[cef-build] platform flag: $PLATFORM_FLAG"
|
||||
echo "[cef-build] GN_DEFINES: $GN_DEFINES"
|
||||
echo "[cef-build] build dir: $CEF_BUILD_DIR"
|
||||
|
||||
python3 cef/tools/automate/automate-git.py \
|
||||
--download-dir="$CEF_BUILD_DIR/chromium" \
|
||||
--depot-tools-dir="$CEF_BUILD_DIR/depot_tools" \
|
||||
--branch="$CEF_BRANCH" \
|
||||
--no-debug-build \
|
||||
--no-distrib-docs \
|
||||
--minimal-distrib \
|
||||
"$PLATFORM_FLAG"
|
||||
|
||||
echo "[cef-build] done. Distrib artefacts at:"
|
||||
echo "[cef-build] $CEF_BUILD_DIR/chromium/src/cef/binary_distrib/"
|
||||
ls -lh "$CEF_BUILD_DIR/chromium/src/cef/binary_distrib/" 2>/dev/null | grep "cef_binary_.*_minimal" || true
|
||||
|
||||
echo
|
||||
echo "[cef-build] next step:"
|
||||
echo "[cef-build] ./scripts/cef-with-codecs/install-local.sh"
|
||||
echo "[cef-build] then:"
|
||||
echo "[cef-build] pnpm dev:app # cargo will pick up the codec-enabled binary via CEF_PATH"
|
||||
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env bash
|
||||
# Drop the codec-enabled CEF binary built by `build-cef-with-codecs.sh`
|
||||
# (or downloaded from a private CDN) into the cache that tauri-cef's
|
||||
# build script expects. After this runs, `cargo build` from any worktree
|
||||
# picks up the new binary via the existing `CEF_PATH` rerun-if-env-changed
|
||||
# wiring in `cef-dll-sys/build.rs`.
|
||||
#
|
||||
# Tracks #1223. Idempotent — running twice replaces the previous extract.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# --- Inputs ---------------------------------------------------------
|
||||
|
||||
# `CEF_PATH` is the same env var the runtime reads via download-cef +
|
||||
# tauri-cli. Default matches the path baked into `scripts/ensure-tauri-cli.sh`.
|
||||
CEF_PATH="${CEF_PATH:-$HOME/Library/Caches/tauri-cef}"
|
||||
|
||||
# Source tarball — by default we look in the build dir produced by
|
||||
# build-cef-with-codecs.sh. Override CEF_TARBALL to install a tarball
|
||||
# downloaded from a private CDN.
|
||||
CEF_BUILD_DIR="${CEF_BUILD_DIR:-$HOME/cef-build}"
|
||||
CEF_TARBALL="${CEF_TARBALL:-}"
|
||||
|
||||
# Match the version pin in `app/src-tauri/Cargo.toml` (`cef = "=146.4.1"`
|
||||
# → binary 146.0.9). When you bump cef, update this string too.
|
||||
CEF_VERSION="${CEF_VERSION:-146.0.9}"
|
||||
|
||||
# --- Detect platform-specific tarball + dest dir --------------------
|
||||
|
||||
case "$(uname -s)" in
|
||||
Darwin)
|
||||
case "$(uname -m)" in
|
||||
arm64|aarch64) PLATFORM_SUFFIX="macosarm64"; DEST_DIR_NAME="cef_macos_aarch64" ;;
|
||||
x86_64) PLATFORM_SUFFIX="macosx64"; DEST_DIR_NAME="cef_macos_x86_64" ;;
|
||||
*) echo "[cef-install] unsupported macOS arch: $(uname -m)" >&2; exit 1 ;;
|
||||
esac
|
||||
;;
|
||||
Linux)
|
||||
case "$(uname -m)" in
|
||||
aarch64|arm64) PLATFORM_SUFFIX="linuxarm64"; DEST_DIR_NAME="cef_linux_aarch64" ;;
|
||||
x86_64) PLATFORM_SUFFIX="linux64"; DEST_DIR_NAME="cef_linux_x86_64" ;;
|
||||
*) echo "[cef-install] unsupported Linux arch: $(uname -m)" >&2; exit 1 ;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "[cef-install] unsupported host: $(uname -s)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Locate the tarball ---------------------------------------------
|
||||
|
||||
if [[ -z "$CEF_TARBALL" ]]; then
|
||||
CEF_TARBALL="$(ls "$CEF_BUILD_DIR/chromium/src/cef/binary_distrib/cef_binary_${CEF_VERSION}"*"_${PLATFORM_SUFFIX}_minimal.tar.bz2" 2>/dev/null | head -n1 || true)"
|
||||
fi
|
||||
|
||||
if [[ -z "$CEF_TARBALL" || ! -f "$CEF_TARBALL" ]]; then
|
||||
echo "[cef-install] no tarball found." >&2
|
||||
echo "[cef-install] expected:" >&2
|
||||
echo "[cef-install] $CEF_BUILD_DIR/chromium/src/cef/binary_distrib/cef_binary_${CEF_VERSION}*_${PLATFORM_SUFFIX}_minimal.tar.bz2" >&2
|
||||
echo "[cef-install] override with: CEF_TARBALL=/path/to/tarball $0" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[cef-install] tarball: $CEF_TARBALL"
|
||||
|
||||
# --- Verify the binary actually has codecs --------------------------
|
||||
|
||||
# Quick sanity check before we extract — if the tarball is somehow the
|
||||
# stock Spotify CDN one (no codecs) we don't want to silently overwrite
|
||||
# a working install. The presence of `libffmpeg.dylib` containing the
|
||||
# string `proprietary` is the cheapest tell.
|
||||
TARBALL_NAME="$(basename "$CEF_TARBALL")"
|
||||
case "$TARBALL_NAME" in
|
||||
*_minimal.tar.bz2) ;;
|
||||
*)
|
||||
echo "[cef-install] WARNING: tarball name doesn't match the *_minimal.tar.bz2 convention." >&2
|
||||
echo "[cef-install] name = $TARBALL_NAME" >&2
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Extract to CEF_PATH/<version>/<platform-dir>/ ------------------
|
||||
|
||||
DEST="$CEF_PATH/$CEF_VERSION/$DEST_DIR_NAME"
|
||||
|
||||
if [[ -d "$DEST" ]]; then
|
||||
echo "[cef-install] removing existing $DEST"
|
||||
rm -rf "$DEST"
|
||||
fi
|
||||
mkdir -p "$DEST"
|
||||
|
||||
echo "[cef-install] extracting → $DEST"
|
||||
tar -xjf "$CEF_TARBALL" -C "$DEST" --strip-components=1
|
||||
|
||||
# --- Verify codec gates -------------------------------------------
|
||||
|
||||
# `MEDIA_OPTIONS_FFMPEG_BRANDING` is what Chromium checks at runtime
|
||||
# to know whether ffmpeg has H.264 etc. The minimal distrib includes
|
||||
# the libcef binary itself — grep for the symbol so we can fail loud
|
||||
# rather than silently install a stock build into the codec slot.
|
||||
LIBCEF_PATH=""
|
||||
case "$(uname -s)" in
|
||||
Darwin) LIBCEF_PATH="$DEST/Release/Chromium Embedded Framework.framework/Libraries/libcef.dylib";;
|
||||
Linux) LIBCEF_PATH="$DEST/Release/libcef.so";;
|
||||
esac
|
||||
|
||||
if [[ -n "$LIBCEF_PATH" && -f "$LIBCEF_PATH" ]]; then
|
||||
if strings "$LIBCEF_PATH" 2>/dev/null | grep -q "Chrome.*ffmpeg\|avc1\.64\|H264VideoStreamParser"; then
|
||||
echo "[cef-install] codec strings detected in libcef → looks like a Chrome-branded build."
|
||||
else
|
||||
echo "[cef-install] WARNING: no proprietary-codec strings detected in $LIBCEF_PATH" >&2
|
||||
echo "[cef-install] install will proceed but Gmeet dynamic-bg may still fail" >&2
|
||||
echo "[cef-install] (run \`node scripts/diagnose-cef-runtime.mjs probe\` after \`pnpm dev:app\`" >&2
|
||||
echo "[cef-install] to confirm h264_baseline === true)" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[cef-install] done."
|
||||
echo "[cef-install] destination: $DEST"
|
||||
echo "[cef-install] next step: pnpm dev:app # cargo build will pick this up via CEF_PATH"
|
||||
Reference in New Issue
Block a user