mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-06 08:01:03 +03:00
The root --service must never load libEGL/libGLESv2: the point of the split is that it exports the scanout dma-buf and the unprivileged --server converts. Two paths could still break that, both because the loader accepted a library too old to export. drm_prewarm() called grab() when the loaded .so had no drmtap_grab_desc, and grab() maps and detiles, so the privileged process pulled in the vendor GL stack at startup, before any consumer had asked for a frame. The per-connection capture loop then did the same for every frame, through the CPU fallback. The version guard could not prevent it: it compared the ABI major only, and this library is still 0.x, so every release it has ever made passed. Add a floor at 0.4.9, where the split entry points landed, and require the three split symbols, which also rejects a build that reports a new enough version without carrying them. That is not hypothetical: a pre-release stamped 0.4.15 shipped without the multi-GPU accessors. Both refusals fall back to PipeWire/portal and say which file and which symbols, at warn level. The split symbols are no longer Options, so the type system carries the guarantee instead of a convention. What is left of the CPU path is only what it was meant to be: the consumer has no render node of its own, or the seat exports no transferable dma-buf. Both are facts about the hardware, with no alternative that keeps the stream, and neither is a property of which file was on the load path. Verified against the real library on i915. With 0.4.15 the export path captures a tiled XR30 scanout and libEGL stays out of /proc/self/maps, while the old grab() branch maps it, so the finding reproduces. A stub reporting 0.4.8 and a stub reporting 0.4.15 without the split symbols are both refused, each with its own diagnostic. The mirrored repr(C) layouts are unchanged across 0.4.9 to 0.4.15, checked field by field against include/drmtap.h at both ends, so the floor costs no compatibility that was real.
274 lines
11 KiB
YAML
274 lines
11 KiB
YAML
name: DRM capture (opt-in drm feature)
|
|
|
|
# Everything CI-side about the opt-in `drm` backend lives here, so the stock CI and release workflows
|
|
# stay byte-identical to a build with the feature off. Nothing in this file runs unless a drm-related
|
|
# path changes (or someone dispatches it by hand), so a PR that does not touch the backend pays nothing.
|
|
#
|
|
# The stock `CI` workflow deliberately does NOT compile with `--features drm`: the shipped default is
|
|
# the drm-off configuration and that stays the primary verified one.
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
pull_request:
|
|
paths:
|
|
- "libs/scrap/src/common/drm_reader.rs"
|
|
- "libs/scrap/src/common/drm_render.rs"
|
|
- "libs/scrap/src/common/drmtap_dl.rs"
|
|
- "libs/scrap/src/common/mod.rs"
|
|
- "libs/scrap/Cargo.toml"
|
|
- "src/ipc.rs"
|
|
- "src/ipc/**"
|
|
- "src/server/drm_capturer.rs"
|
|
- "src/server/wayland.rs"
|
|
- "src/server/display_service.rs"
|
|
- "build.py"
|
|
- ".github/workflows/drm-capture.yml"
|
|
push:
|
|
branches:
|
|
- master
|
|
paths:
|
|
- "libs/scrap/src/common/drm_reader.rs"
|
|
- "libs/scrap/src/common/drm_render.rs"
|
|
- "libs/scrap/src/common/drmtap_dl.rs"
|
|
- "src/ipc.rs"
|
|
- "src/ipc/**"
|
|
- "src/server/drm_capturer.rs"
|
|
- "build.py"
|
|
- ".github/workflows/drm-capture.yml"
|
|
|
|
env:
|
|
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
|
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
|
|
FLUTTER_VERSION: "3.24.5"
|
|
|
|
jobs:
|
|
drm-tests:
|
|
name: drm unit tests (linux)
|
|
runs-on: ubuntu-24.04
|
|
steps:
|
|
- name: Free Disk Space (Ubuntu)
|
|
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
|
|
with:
|
|
tool-cache: false
|
|
android: true
|
|
dotnet: true
|
|
haskell: true
|
|
large-packages: false
|
|
swap-storage: false
|
|
|
|
- name: Checkout source code
|
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Install prerequisites
|
|
shell: bash
|
|
run: |
|
|
sudo apt-get -y update
|
|
sudo apt-get install -y \
|
|
clang cmake curl gcc git g++ \
|
|
libpam0g-dev libasound2-dev libunwind-dev \
|
|
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
|
|
libgtk-3-dev libpulse-dev libva-dev libvdpau-dev \
|
|
libxcb-randr0-dev libxcb-shape0-dev libxcb-xfixes0-dev \
|
|
libxdo-dev libxfixes-dev nasm wget
|
|
|
|
- name: Setup vcpkg with Github Actions binary cache
|
|
uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11
|
|
with:
|
|
vcpkgDirectory: /opt/artifacts/vcpkg
|
|
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
|
|
|
- name: Install vcpkg dependencies
|
|
shell: bash
|
|
run: $VCPKG_ROOT/vcpkg install --x-install-root="$VCPKG_ROOT/installed"
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
|
|
with:
|
|
toolchain: stable
|
|
targets: x86_64-unknown-linux-gnu
|
|
|
|
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
|
|
# The whole rustdesk-crate test set with the feature ON, not just the `_drm` ones by name: a name
|
|
# filter would skip the sibling asserts that also matter in this configuration, notably the one
|
|
# bounding `size_of::<Data>()`, which the new DmabufDesc variant grows.
|
|
# The two skips are the same ones the stock CI applies: both need a real display server and fail
|
|
# on a headless runner regardless of this feature.
|
|
- name: Run rustdesk crate tests with the drm feature
|
|
shell: bash
|
|
run: |
|
|
cargo test --locked --target x86_64-unknown-linux-gnu -p rustdesk --features drm \
|
|
--no-fail-fast -- --skip test_get_cursor_pos --skip test_get_key_state
|
|
|
|
# The capture backend itself lives in the scrap crate, so its unit tests are a separate
|
|
# package. `--lib` keeps this to unit tests; none of them touch a device or a display server.
|
|
- name: Run scrap crate tests with the drm feature
|
|
shell: bash
|
|
run: |
|
|
cargo test --locked --target x86_64-unknown-linux-gnu -p scrap --features drm --lib
|
|
|
|
libdrmtap:
|
|
name: libdrmtap pin, build and .so contract
|
|
runs-on: ubuntu-24.04
|
|
steps:
|
|
- name: Checkout source code
|
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
|
|
- name: Install libdrmtap build deps
|
|
shell: bash
|
|
run: |
|
|
sudo apt-get -y update
|
|
sudo apt-get install -y meson ninja-build pkg-config libdrm-dev \
|
|
libegl1-mesa-dev libgles2-mesa-dev
|
|
|
|
# Exercises the real fetch-and-build path in build.py, which pins the commit by sha, so a bad or
|
|
# moved pin fails here rather than in a release job.
|
|
- name: Fetch the pinned libdrmtap and build the .so
|
|
shell: bash
|
|
run: |
|
|
python3 - <<'PY'
|
|
import importlib.util, sys
|
|
spec = importlib.util.spec_from_file_location("b", "build.py")
|
|
b = importlib.util.module_from_spec(spec)
|
|
sys.argv = ["build.py"]
|
|
spec.loader.exec_module(b)
|
|
so = b.build_libdrmtap_so()
|
|
print(f"::notice::built {so}")
|
|
open("so_path", "w").write(so)
|
|
PY
|
|
|
|
# The shipped hot path is the EGL detile. libdrmtap degrades to a CPU-only stub when the egl or
|
|
# glesv2 pkg-config files are missing on the build host, and nothing else in the pipeline notices,
|
|
# so assert here that the object we would ship really carries EGL and really exports every symbol
|
|
# the runtime loader resolves.
|
|
- name: Assert the .so contract (EGL enabled, loader symbols present)
|
|
shell: bash
|
|
run: |
|
|
SO="$(cat so_path)"
|
|
echo "checking $SO"
|
|
missing=0
|
|
# Every symbol drmtap_dl.rs resolves, derived from the loader itself so the two cannot drift.
|
|
for sym in $(grep -oE 'b"drmtap_[a-z_]+"' libs/scrap/src/common/drmtap_dl.rs \
|
|
| sed 's/^b"//; s/"$//' | sort -u); do
|
|
if ! nm -D --defined-only "$SO" | grep -q " T $sym\$"; then
|
|
echo "::error::libdrmtap does not export $sym, which the runtime loader resolves"
|
|
missing=1
|
|
fi
|
|
done
|
|
# EGL is reached by lazy dlopen, on purpose, so that the privileged process never links the
|
|
# vendor GL stack. That means there is NO DT_NEEDED entry and no undefined egl* symbol to look
|
|
# for: the naive ELF check reports "no EGL" on a perfectly good library. What a CPU-only stub
|
|
# build really lacks is the dlopen target name and the import call itself.
|
|
for s in "libEGL.so.1" "eglCreateImageKHR"; do
|
|
if ! strings "$SO" | grep -qF "$s"; then
|
|
echo "::error::libdrmtap looks like a CPU-only stub (no $s): the EGL detile hot path is missing"
|
|
missing=1
|
|
fi
|
|
done
|
|
test "$missing" -eq 0
|
|
echo "::notice::libdrmtap .so contract ok (13 loader symbols, EGL detile present)"
|
|
|
|
# The bridge generator is a reusable workflow, so this calls the stock one instead of duplicating it.
|
|
generate-bridge:
|
|
uses: ./.github/workflows/bridge.yml
|
|
|
|
drm-deb:
|
|
name: unattended-wayland deb (verification build)
|
|
needs: generate-bridge
|
|
runs-on: ubuntu-24.04
|
|
steps:
|
|
- name: Free Disk Space (Ubuntu)
|
|
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
|
|
with:
|
|
tool-cache: false
|
|
android: true
|
|
dotnet: true
|
|
haskell: true
|
|
large-packages: false
|
|
swap-storage: false
|
|
|
|
- name: Checkout source code
|
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Restore bridge files
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: bridge-artifact
|
|
path: ./
|
|
|
|
- name: Install prerequisites
|
|
shell: bash
|
|
run: |
|
|
sudo apt-get -y update
|
|
# Same list the stock linux job needs, plus the flutter desktop toolchain and the three
|
|
# libdrmtap build deps (libdrm and the mesa-specific EGL/GLES dev packages).
|
|
sudo apt-get install -y \
|
|
clang cmake curl gcc git g++ ninja-build meson pkg-config \
|
|
libpam0g-dev libasound2-dev libunwind-dev liblzma-dev \
|
|
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
|
|
libgtk-3-dev libpulse-dev libva-dev libvdpau-dev \
|
|
libxcb-randr0-dev libxcb-shape0-dev libxcb-xfixes0-dev \
|
|
libxdo-dev libxfixes-dev nasm wget \
|
|
libdrm-dev libegl1-mesa-dev libgles2-mesa-dev
|
|
|
|
- name: Setup vcpkg with Github Actions binary cache
|
|
uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11
|
|
with:
|
|
vcpkgDirectory: /opt/artifacts/vcpkg
|
|
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
|
|
|
- name: Install vcpkg dependencies
|
|
shell: bash
|
|
run: $VCPKG_ROOT/vcpkg install --x-install-root="$VCPKG_ROOT/installed"
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
|
|
with:
|
|
toolchain: stable
|
|
targets: x86_64-unknown-linux-gnu
|
|
|
|
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
|
|
- name: Setup flutter
|
|
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
|
|
with:
|
|
channel: "stable"
|
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
|
|
- name: Patch flutter
|
|
shell: bash
|
|
run: |
|
|
cd $(dirname $(dirname $(which flutter)))
|
|
[[ "3.24.5" == ${{ env.FLUTTER_VERSION }} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
|
|
|
- name: Build the unattended-wayland deb
|
|
shell: bash
|
|
run: |
|
|
cargo build --locked --lib --release --features hwcodec,flutter,unix-file-copy-paste,drm
|
|
python3 ./build.py --flutter --drm --skip-cargo
|
|
|
|
# build.py exits 0 on some inner failures, so assert the artifact instead of trusting the status,
|
|
# and assert the two things that make it the drm variant at all.
|
|
- name: Assert the deb is a real drm build
|
|
shell: bash
|
|
run: |
|
|
deb="$(ls rustdesk-unattended-wayland-*.deb)"
|
|
test -n "$deb"
|
|
echo "::notice::built $deb ($(stat -c %s "$deb") bytes)"
|
|
dpkg -c "$deb" | grep -E 'usr/lib/rustdesk/libdrmtap\.so\.0\.[0-9]+\.[0-9]+$'
|
|
dpkg -c "$deb" | grep -E 'usr/lib/rustdesk/libdrmtap\.so\.0 ->'
|
|
mv "$deb" "${deb%.deb}-x86_64.deb"
|
|
|
|
# Verification artifact, deliberately NOT a release deliverable: it is built on the runner rather
|
|
# than in the old container the stock release debs use, so its glibc floor is higher. The consent
|
|
# free variant stays out of the published release either way.
|
|
- name: Upload the deb
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: rustdesk-unattended-wayland-x86_64.deb
|
|
path: rustdesk-unattended-wayland-*-x86_64.deb
|