livekit_client: Screensharing on Niri + NixOS (#52017)

Cameron Mcloughlin and Jakub Konka created

Release Notes:

- Fixed a weird niche interaction between niri and nixos that broke
screensharing

---------

Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>

Change summary

Cargo.lock                                        |  1 
Cargo.toml                                        |  2 +
crates/livekit_client/Cargo.toml                  |  1 
crates/livekit_client/src/livekit_client/linux.rs | 18 +++++++++++++++-
crates/zed/build.rs                               |  4 ++
nix/build.nix                                     |  9 ++++---
6 files changed, 28 insertions(+), 7 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -10004,6 +10004,7 @@ dependencies = [
  "tokio",
  "ui",
  "util",
+ "webrtc-sys",
  "zed-scap",
 ]
 

Cargo.toml 🔗

@@ -779,6 +779,7 @@ wax = "0.7"
 which = "6.0.0"
 wasm-bindgen = "0.2.113"
 web-time = "1.1.0"
+webrtc-sys = "0.3.23"
 wgpu = { git = "https://github.com/zed-industries/wgpu.git", branch = "v29" }
 windows-core = "0.61"
 yawc = "0.2.5"
@@ -849,6 +850,7 @@ windows-capture = { git = "https://github.com/zed-industries/windows-capture.git
 calloop = { git = "https://github.com/zed-industries/calloop" }
 livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "c1209aa155cbf4543383774f884a46ae7e53ee2e" }
 libwebrtc = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "c1209aa155cbf4543383774f884a46ae7e53ee2e" }
+webrtc-sys = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "c1209aa155cbf4543383774f884a46ae7e53ee2e" }
 
 [profile.dev]
 split-debuginfo = "unpacked"

crates/livekit_client/Cargo.toml 🔗

@@ -49,6 +49,7 @@ livekit.workspace = true
 
 [target.'cfg(target_os = "linux")'.dependencies]
 tokio = { workspace = true, features = ["time"] }
+webrtc-sys.workspace = true
 
 [target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))'.dependencies]
 scap.workspace = true

crates/livekit_client/src/livekit_client/linux.rs 🔗

@@ -14,6 +14,7 @@ use std::sync::{
 };
 
 static NEXT_WAYLAND_SHARE_ID: AtomicU64 = AtomicU64::new(1);
+const PIPEWIRE_TIMEOUT_S: u64 = 30;
 
 pub struct WaylandScreenCaptureStream {
     id: u64,
@@ -64,6 +65,17 @@ pub(crate) async fn start_wayland_desktop_capture(
     };
     use libwebrtc::native::yuv_helper::argb_to_nv12;
     use std::time::Duration;
+    use webrtc_sys::webrtc::ffi as webrtc_ffi;
+
+    fn webrtc_log_callback(message: String, severity: webrtc_ffi::LoggingSeverity) {
+        match severity {
+            webrtc_ffi::LoggingSeverity::Error => log::error!("[webrtc] {}", message.trim()),
+            _ => log::debug!("[webrtc] {}", message.trim()),
+        }
+    }
+
+    let _webrtc_log_sink = webrtc_ffi::new_log_sink(webrtc_log_callback);
+    log::debug!("Wayland desktop capture: WebRTC internal logging enabled");
 
     let stop_flag = Arc::new(AtomicBool::new(false));
     let (mut video_source_tx, mut video_source_rx) = mpsc::channel::<NativeVideoSource>(1);
@@ -79,7 +91,6 @@ pub(crate) async fn start_wayland_desktop_capture(
     })?;
 
     let permanent_error = Arc::new(AtomicBool::new(false));
-
     let stop_cb = stop_flag.clone();
     let permanent_error_cb = permanent_error.clone();
     capturer.start_capture(None, {
@@ -136,6 +147,8 @@ pub(crate) async fn start_wayland_desktop_capture(
         }
     });
 
+    log::info!("Wayland desktop capture: starting capture loop");
+
     let stop = stop_flag.clone();
     let tokio_task = gpui_tokio::Tokio::spawn(cx, async move {
         loop {
@@ -162,10 +175,11 @@ pub(crate) async fn start_wayland_desktop_capture(
     let executor = cx.background_executor().clone();
     let video_source = video_source_rx
         .next()
-        .with_timeout(Duration::from_secs(15), &executor)
+        .with_timeout(Duration::from_secs(PIPEWIRE_TIMEOUT_S), &executor)
         .await
         .map_err(|_| {
             stop_flag.store(true, Ordering::Relaxed);
+            log::error!("Wayland desktop capture timed out.");
             anyhow::anyhow!(
                 "Screen sharing timed out waiting for the first frame. \
                  Check that xdg-desktop-portal and PipeWire are running, \

crates/zed/build.rs 🔗

@@ -7,12 +7,14 @@ fn main() {
         // Add rpaths for libraries that webrtc-sys dlopens at runtime.
         // This is mostly required for hosts with non-standard SO installation
         // locations such as NixOS.
-        let dlopened_libs = ["libva", "libva-drm"];
+        let dlopened_libs = ["libva", "libva-drm", "egl"];
 
         let mut rpath_dirs = std::collections::BTreeSet::new();
         for lib in &dlopened_libs {
             if let Some(libdir) = pkg_config::get_variable(lib, "libdir").ok() {
                 rpath_dirs.insert(libdir);
+            } else {
+                eprintln!("zed build.rs: {lib} not found in pkg-config's path");
             }
         }
 

nix/build.nix 🔗

@@ -77,7 +77,6 @@ let
     builtins.elem firstComp topLevelIncludes;
 
   craneLib = crane.overrideToolchain rustToolchain;
-  gpu-lib = if withGLES then libglvnd else vulkan-loader;
   commonArgs =
     let
       zedCargoLock = builtins.fromTOML (builtins.readFile ../crates/zed/Cargo.toml);
@@ -179,7 +178,8 @@ let
         libva
         libxkbcommon
         wayland
-        gpu-lib
+        libglvnd
+        vulkan-loader
         xorg.libX11
         xorg.libxcb
         libdrm
@@ -236,7 +236,8 @@ let
         # about them that's special is that they're manually dlopened at runtime
         NIX_LDFLAGS = lib.optionalString stdenv'.hostPlatform.isLinux "-rpath ${
           lib.makeLibraryPath [
-            gpu-lib
+            libglvnd
+            vulkan-loader
             wayland
             libva
           ]
@@ -245,7 +246,7 @@ let
         NIX_OUTPATH_USED_AS_RANDOM_SEED = "norebuilds";
       };
 
-      # prevent nix from removing the "unused" wayland/gpu-lib rpaths
+      # prevent nix from removing the "unused" wayland rpaths
       dontPatchELF = stdenv'.hostPlatform.isLinux;
 
       # TODO: try craneLib.cargoNextest separate output