Windows screen sharing (#34223)

Max Brunsfeld , localcc , and Peter Tripp created

Release Notes:

- N/A

---------

Co-authored-by: localcc <work@localcc.cc>
Co-authored-by: Peter Tripp <petertripp@gmail.com>

Change summary

Cargo.lock                                           |  4 +
Cargo.toml                                           |  2 
crates/gpui/Cargo.toml                               |  4 +
crates/gpui/src/platform.rs                          |  9 ++
crates/gpui/src/platform/windows.rs                  |  3 +
crates/gpui/src/platform/windows/platform.rs         |  6 -
crates/livekit_client/Cargo.toml                     |  4 
crates/livekit_client/src/lib.rs                     | 30 +--------
crates/livekit_client/src/livekit_client/playback.rs | 42 +++++++++-----
tooling/workspace-hack/Cargo.toml                    |  0 
10 files changed, 53 insertions(+), 51 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -14097,7 +14097,7 @@ dependencies = [
 [[package]]
 name = "scap"
 version = "0.0.8"
-source = "git+https://github.com/zed-industries/scap?rev=08f0a01417505cc0990b9931a37e5120db92e0d0#08f0a01417505cc0990b9931a37e5120db92e0d0"
+source = "git+https://github.com/zed-industries/scap?rev=28dd306ff2e3374404936dec778fc1e975b8dd12#28dd306ff2e3374404936dec778fc1e975b8dd12"
 dependencies = [
  "anyhow",
  "cocoa 0.25.0",
@@ -19693,7 +19693,9 @@ dependencies = [
  "wasmtime-cranelift",
  "wasmtime-environ",
  "winapi",
+ "windows 0.61.1",
  "windows-core 0.61.0",
+ "windows-future",
  "windows-numerics",
  "windows-sys 0.48.0",
  "windows-sys 0.52.0",

Cargo.toml 🔗

@@ -546,7 +546,7 @@ rustc-demangle = "0.1.23"
 rustc-hash = "2.1.0"
 rustls = { version = "0.23.26" }
 rustls-platform-verifier = "0.5.0"
-scap = { git = "https://github.com/zed-industries/scap", rev = "08f0a01417505cc0990b9931a37e5120db92e0d0", default-features = false }
+scap = { git = "https://github.com/zed-industries/scap", rev = "28dd306ff2e3374404936dec778fc1e975b8dd12", default-features = false }
 schemars = { version = "1.0", features = ["indexmap2"] }
 semver = "1.0"
 serde = { version = "1.0", features = ["derive", "rc"] }

crates/gpui/Cargo.toml 🔗

@@ -150,6 +150,9 @@ metal.workspace = true
 [target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))'.dependencies]
 pathfinder_geometry = "0.5"
 
+[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))'.dependencies]
+scap = { workspace = true, optional = true }
+
 [target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
 # Always used
 flume = "0.11"
@@ -168,7 +171,6 @@ cosmic-text = { version = "0.14.0", optional = true }
 font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5474cfad4b719a72ec8ed2cb7327b2b01fd10568", features = [
     "source-fontconfig-dlopen",
 ], optional = true }
-scap = { workspace = true, optional = true }
 
 calloop = { version = "0.13.0" }
 filedescriptor = { version = "0.8.2", optional = true }

crates/gpui/src/platform.rs 🔗

@@ -26,8 +26,13 @@ mod windows;
 
 #[cfg(all(
     feature = "screen-capture",
-    any(target_os = "linux", target_os = "freebsd"),
-    any(feature = "wayland", feature = "x11"),
+    any(
+        target_os = "windows",
+        all(
+            any(target_os = "linux", target_os = "freebsd"),
+            any(feature = "wayland", feature = "x11"),
+        )
+    )
 ))]
 pub(crate) mod scap_screen_capture;
 

crates/gpui/src/platform/windows.rs 🔗

@@ -26,4 +26,7 @@ pub(crate) use wrapper::*;
 
 pub(crate) use windows::Win32::Foundation::HWND;
 
+#[cfg(feature = "screen-capture")]
+pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
+#[cfg(not(feature = "screen-capture"))]
 pub(crate) type PlatformScreenCaptureFrame = ();

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -434,16 +434,14 @@ impl Platform for WindowsPlatform {
 
     #[cfg(feature = "screen-capture")]
     fn is_screen_capture_supported(&self) -> bool {
-        false
+        true
     }
 
     #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
     ) -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>> {
-        let (mut tx, rx) = oneshot::channel();
-        tx.send(Err(anyhow!("screen capture not implemented"))).ok();
-        rx
+        crate::platform::scap_screen_capture::scap_screen_sources(&self.foreground_executor)
     }
 
     fn active_window(&self) -> Option<AnyWindowHandle> {

crates/livekit_client/Cargo.toml 🔗

@@ -25,7 +25,7 @@ async-trait.workspace = true
 collections.workspace = true
 cpal.workspace = true
 futures.workspace = true
-gpui = { workspace = true, features = ["screen-capture", "x11", "wayland"] }
+gpui = { workspace = true, features = ["screen-capture", "x11", "wayland", "windows-manifest"] }
 gpui_tokio.workspace = true
 http_client_tls.workspace = true
 image.workspace = true
@@ -45,7 +45,7 @@ livekit = { rev = "d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4", git = "https://git
     "__rustls-tls"
 ] }
 
-[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
+[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))'.dependencies]
 scap.workspace = true
 
 [target.'cfg(target_os = "macos")'.dependencies]

crates/livekit_client/src/lib.rs 🔗

@@ -3,36 +3,16 @@ use collections::HashMap;
 mod remote_video_track_view;
 pub use remote_video_track_view::{RemoteVideoTrackView, RemoteVideoTrackViewEvent};
 
-#[cfg(not(any(
-    test,
-    feature = "test-support",
-    any(all(target_os = "windows", target_env = "gnu"), target_os = "freebsd")
-)))]
+#[cfg(not(any(test, feature = "test-support", target_os = "freebsd")))]
 mod livekit_client;
-#[cfg(not(any(
-    test,
-    feature = "test-support",
-    any(all(target_os = "windows", target_env = "gnu"), target_os = "freebsd")
-)))]
+#[cfg(not(any(test, feature = "test-support", target_os = "freebsd")))]
 pub use livekit_client::*;
 
-#[cfg(any(
-    test,
-    feature = "test-support",
-    any(all(target_os = "windows", target_env = "gnu"), target_os = "freebsd")
-))]
+#[cfg(any(test, feature = "test-support", target_os = "freebsd"))]
 mod mock_client;
-#[cfg(any(
-    test,
-    feature = "test-support",
-    any(all(target_os = "windows", target_env = "gnu"), target_os = "freebsd")
-))]
+#[cfg(any(test, feature = "test-support", target_os = "freebsd"))]
 pub mod test;
-#[cfg(any(
-    test,
-    feature = "test-support",
-    any(all(target_os = "windows", target_env = "gnu"), target_os = "freebsd")
-))]
+#[cfg(any(test, feature = "test-support", target_os = "freebsd"))]
 pub use mock_client::*;
 
 #[derive(Debug, Clone)]

crates/livekit_client/src/livekit_client/playback.rs 🔗

@@ -585,10 +585,10 @@ fn video_frame_buffer_from_webrtc(buffer: Box<dyn VideoBuffer>) -> Option<Remote
         if start_ptr.is_null() {
             return None;
         }
-        let bgra_frame_slice = std::slice::from_raw_parts_mut(start_ptr, byte_len);
+        let argb_frame_slice = std::slice::from_raw_parts_mut(start_ptr, byte_len);
         buffer.to_argb(
-            VideoFormatType::ARGB, // For some reason, this displays correctly while RGBA (the correct format) does not
-            bgra_frame_slice,
+            VideoFormatType::ARGB,
+            argb_frame_slice,
             stride,
             width as i32,
             height as i32,
@@ -596,12 +596,13 @@ fn video_frame_buffer_from_webrtc(buffer: Box<dyn VideoBuffer>) -> Option<Remote
         Vec::from_raw_parts(start_ptr, byte_len, byte_len)
     };
 
+    // TODO: Unclear why providing argb_image to RgbaImage works properly.
+    let image = RgbaImage::from_raw(width, height, argb_image)
+        .with_context(|| "Bug: not enough bytes allocated for image.")
+        .log_err()?;
+
     Some(Arc::new(RenderImage::new(SmallVec::from_elem(
-        Frame::new(
-            RgbaImage::from_raw(width, height, argb_image)
-                .with_context(|| "Bug: not enough bytes allocated for image.")
-                .log_err()?,
-        ),
+        Frame::new(image),
         1,
     ))))
 }
@@ -617,9 +618,9 @@ fn video_frame_buffer_to_webrtc(frame: ScreenCaptureFrame) -> Option<impl AsRef<
     }
 }
 
-#[cfg(any(target_os = "linux", target_os = "freebsd"))]
+#[cfg(not(target_os = "macos"))]
 fn video_frame_buffer_to_webrtc(frame: ScreenCaptureFrame) -> Option<impl AsRef<dyn VideoBuffer>> {
-    use libwebrtc::native::yuv_helper::argb_to_nv12;
+    use libwebrtc::native::yuv_helper::{abgr_to_nv12, argb_to_nv12};
     use livekit::webrtc::prelude::NV12Buffer;
     match frame.0 {
         scap::frame::Frame::BGRx(frame) => {
@@ -638,6 +639,22 @@ fn video_frame_buffer_to_webrtc(frame: ScreenCaptureFrame) -> Option<impl AsRef<
             );
             Some(buffer)
         }
+        scap::frame::Frame::RGBx(frame) => {
+            let mut buffer = NV12Buffer::new(frame.width as u32, frame.height as u32);
+            let (stride_y, stride_uv) = buffer.strides();
+            let (data_y, data_uv) = buffer.data_mut();
+            abgr_to_nv12(
+                &frame.data,
+                frame.width as u32 * 4,
+                data_y,
+                stride_y,
+                data_uv,
+                stride_uv,
+                frame.width,
+                frame.height,
+            );
+            Some(buffer)
+        }
         scap::frame::Frame::YUVFrame(yuvframe) => {
             let mut buffer = NV12Buffer::with_strides(
                 yuvframe.width as u32,
@@ -659,11 +676,6 @@ fn video_frame_buffer_to_webrtc(frame: ScreenCaptureFrame) -> Option<impl AsRef<
     }
 }
 
-#[cfg(target_os = "windows")]
-fn video_frame_buffer_to_webrtc(_frame: ScreenCaptureFrame) -> Option<impl AsRef<dyn VideoBuffer>> {
-    None as Option<Box<dyn VideoBuffer>>
-}
-
 trait DeviceChangeListenerApi: Stream<Item = ()> + Sized {
     fn new(input: bool) -> Result<Self>;
 }

tooling/workspace-hack/Cargo.toml 🔗

@@ -574,7 +574,9 @@ tokio-socks = { version = "0.5", features = ["futures-io"] }
 tokio-stream = { version = "0.1", features = ["fs"] }
 tower = { version = "0.5", default-features = false, features = ["timeout", "util"] }
 winapi = { version = "0.3", default-features = false, features = ["cfg", "commapi", "consoleapi", "errhandlingapi", "evntrace", "fileapi", "handleapi", "impl-debug", "impl-default", "in6addr", "inaddr", "ioapiset", "knownfolders", "minwinbase", "minwindef", "namedpipeapi", "ntsecapi", "objbase", "processenv", "processthreadsapi", "shlobj", "std", "synchapi", "sysinfoapi", "timezoneapi", "winbase", "windef", "winerror", "winioctl", "winnt", "winreg", "winsock2", "winuser"] }