gpui: Make screen capture dependency optional (#32937)

Hilmar Wiegand and Michael Sloan created

Add `screen-capture` feature to gpui to enable screen capture support.  The motivation for this is to make dependencies on scap / x11 / xcb optional.

Release Notes:

- N/A

---------

Co-authored-by: Michael Sloan <michael@zed.dev>

Change summary

Cargo.toml                                        |  1 
crates/gpui/Cargo.toml                            |  3 +
crates/gpui/src/platform.rs                       | 19 ++++++++++++++++
crates/gpui/src/platform/linux.rs                 |  4 +-
crates/gpui/src/platform/linux/headless/client.rs | 13 +++++-----
crates/gpui/src/platform/linux/platform.rs        | 10 +++++--
crates/gpui/src/platform/linux/wayland/client.rs  | 17 ++++++++-----
crates/gpui/src/platform/linux/x11/client.rs      | 13 ++++++----
crates/gpui/src/platform/mac.rs                   |  2 +
crates/gpui/src/platform/mac/platform.rs          | 20 +++++++++-------
crates/gpui/src/platform/test/platform.rs         |  2 +
crates/gpui/src/platform/windows/platform.rs      |  2 +
12 files changed, 73 insertions(+), 33 deletions(-)

Detailed changes

Cargo.toml 🔗

@@ -276,6 +276,7 @@ go_to_line = { path = "crates/go_to_line" }
 google_ai = { path = "crates/google_ai" }
 gpui = { path = "crates/gpui", default-features = false, features = [
     "http_client",
+    "screen-capture",
 ] }
 gpui_macros = { path = "crates/gpui_macros" }
 gpui_tokio = { path = "crates/gpui_tokio" }

crates/gpui/Cargo.toml 🔗

@@ -50,7 +50,6 @@ wayland = [
     "filedescriptor",
     "xkbcommon",
     "open",
-    "scap",
 ]
 x11 = [
     "blade-graphics",
@@ -67,6 +66,8 @@ x11 = [
     "x11-clipboard",
     "filedescriptor",
     "open",
+]
+screen-capture = [
     "scap",
 ]
 windows-manifest = []

crates/gpui/src/platform.rs 🔗

@@ -25,6 +25,7 @@ mod test;
 mod windows;
 
 #[cfg(all(
+    feature = "screen-capture",
     any(target_os = "linux", target_os = "freebsd"),
     any(feature = "wayland", feature = "x11"),
 ))]
@@ -176,10 +177,28 @@ pub(crate) trait Platform: 'static {
         None
     }
 
+    #[cfg(feature = "screen-capture")]
     fn is_screen_capture_supported(&self) -> bool;
+    #[cfg(not(feature = "screen-capture"))]
+    fn is_screen_capture_supported(&self) -> bool {
+        false
+    }
+    #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
     ) -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>>;
+    #[cfg(not(feature = "screen-capture"))]
+    fn screen_capture_sources(
+        &self,
+    ) -> oneshot::Receiver<anyhow::Result<Vec<Box<dyn ScreenCaptureSource>>>> {
+        let (sources_tx, sources_rx) = oneshot::channel();
+        sources_tx
+            .send(Err(anyhow::anyhow!(
+                "gpui was compiled without the screen-capture feature"
+            )))
+            .ok();
+        sources_rx
+    }
 
     fn open_window(
         &self,

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

@@ -23,7 +23,7 @@ pub(crate) use wayland::*;
 #[cfg(feature = "x11")]
 pub(crate) use x11::*;
 
-#[cfg(any(feature = "wayland", feature = "x11"))]
+#[cfg(all(feature = "screen-capture", any(feature = "wayland", feature = "x11")))]
 pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
-#[cfg(not(any(feature = "wayland", feature = "x11")))]
+#[cfg(not(all(feature = "screen-capture", any(feature = "wayland", feature = "x11"))))]
 pub(crate) type PlatformScreenCaptureFrame = ();

crates/gpui/src/platform/linux/headless/client.rs 🔗

@@ -1,16 +1,14 @@
 use std::cell::RefCell;
 use std::rc::Rc;
 
-use anyhow::anyhow;
 use calloop::{EventLoop, LoopHandle};
-use futures::channel::oneshot;
 use util::ResultExt;
 
 use crate::platform::linux::LinuxClient;
 use crate::platform::{LinuxCommon, PlatformWindow};
 use crate::{
     AnyWindowHandle, CursorStyle, DisplayId, LinuxKeyboardLayout, PlatformDisplay,
-    PlatformKeyboardLayout, ScreenCaptureSource, WindowParams,
+    PlatformKeyboardLayout, WindowParams,
 };
 
 pub struct HeadlessClientState {
@@ -67,15 +65,18 @@ impl LinuxClient for HeadlessClient {
         None
     }
 
+    #[cfg(feature = "screen-capture")]
     fn is_screen_capture_supported(&self) -> bool {
         false
     }
 
+    #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
-    ) -> oneshot::Receiver<anyhow::Result<Vec<Box<dyn ScreenCaptureSource>>>> {
-        let (mut tx, rx) = oneshot::channel();
-        tx.send(Err(anyhow!(
+    ) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Box<dyn crate::ScreenCaptureSource>>>>
+    {
+        let (mut tx, rx) = futures::channel::oneshot::channel();
+        tx.send(Err(anyhow::anyhow!(
             "Headless mode does not support screen capture."
         )))
         .ok();

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

@@ -26,7 +26,7 @@ use crate::{
     Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
     ForegroundExecutor, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu, PathPromptOptions,
     Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow,
-    Point, Result, ScreenCaptureSource, Task, WindowAppearance, WindowParams, px,
+    Point, Result, Task, WindowAppearance, WindowParams, px,
 };
 
 #[cfg(any(feature = "wayland", feature = "x11"))]
@@ -51,10 +51,12 @@ pub trait LinuxClient {
     #[allow(unused)]
     fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
     fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
+    #[cfg(feature = "screen-capture")]
     fn is_screen_capture_supported(&self) -> bool;
+    #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
-    ) -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>>;
+    ) -> oneshot::Receiver<Result<Vec<Box<dyn crate::ScreenCaptureSource>>>>;
 
     fn open_window(
         &self,
@@ -235,13 +237,15 @@ impl<P: LinuxClient + 'static> Platform for P {
         self.displays()
     }
 
+    #[cfg(feature = "screen-capture")]
     fn is_screen_capture_supported(&self) -> bool {
         self.is_screen_capture_supported()
     }
 
+    #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
-    ) -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>> {
+    ) -> oneshot::Receiver<Result<Vec<Box<dyn crate::ScreenCaptureSource>>>> {
         self.screen_capture_sources()
     }
 

crates/gpui/src/platform/linux/wayland/client.rs 🔗

@@ -7,7 +7,6 @@ use std::{
     time::{Duration, Instant},
 };
 
-use anyhow::anyhow;
 use calloop::{
     EventLoop, LoopHandle,
     timer::{TimeoutAction, Timer},
@@ -15,7 +14,6 @@ use calloop::{
 use calloop_wayland_source::WaylandSource;
 use collections::HashMap;
 use filedescriptor::Pipe;
-use futures::channel::oneshot;
 use http_client::Url;
 use smallvec::SmallVec;
 use util::ResultExt;
@@ -77,8 +75,8 @@ use crate::{
     FileDropEvent, ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon,
     LinuxKeyboardLayout, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
     MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay,
-    PlatformInput, PlatformKeyboardLayout, Point, SCROLL_LINES, ScaledPixels, ScreenCaptureSource,
-    ScrollDelta, ScrollWheelEvent, Size, TouchPhase, WindowParams, point, px, size,
+    PlatformInput, PlatformKeyboardLayout, Point, SCROLL_LINES, ScaledPixels, ScrollDelta,
+    ScrollWheelEvent, Size, TouchPhase, WindowParams, point, px, size,
 };
 use crate::{
     SharedString,
@@ -666,20 +664,25 @@ impl LinuxClient for WaylandClient {
         None
     }
 
+    #[cfg(feature = "screen-capture")]
     fn is_screen_capture_supported(&self) -> bool {
         false
     }
 
+    #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
-    ) -> oneshot::Receiver<anyhow::Result<Vec<Box<dyn ScreenCaptureSource>>>> {
+    ) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Box<dyn crate::ScreenCaptureSource>>>>
+    {
         // TODO: Get screen capture working on wayland. Be sure to try window resizing as that may
         // be tricky.
         //
         // start_scap_default_target_source()
-        let (sources_tx, sources_rx) = oneshot::channel();
+        let (sources_tx, sources_rx) = futures::channel::oneshot::channel();
         sources_tx
-            .send(Err(anyhow!("Wayland screen capture not yet implemented.")))
+            .send(Err(anyhow::anyhow!(
+                "Wayland screen capture not yet implemented."
+            )))
             .ok();
         sources_rx
     }

crates/gpui/src/platform/linux/x11/client.rs 🔗

@@ -15,7 +15,6 @@ use calloop::{
     generic::{FdWrapper, Generic},
 };
 use collections::HashMap;
-use futures::channel::oneshot;
 use http_client::Url;
 use log::Level;
 use smallvec::SmallVec;
@@ -59,13 +58,12 @@ use crate::platform::{
         reveal_path_internal,
         xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
     },
-    scap_screen_capture::scap_screen_sources,
 };
 use crate::{
     AnyWindowHandle, Bounds, ClipboardItem, CursorStyle, DisplayId, FileDropEvent, Keystroke,
     LinuxKeyboardLayout, Modifiers, ModifiersChangedEvent, MouseButton, Pixels, Platform,
     PlatformDisplay, PlatformInput, PlatformKeyboardLayout, Point, RequestFrameOptions,
-    ScaledPixels, ScreenCaptureSource, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
+    ScaledPixels, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
     modifiers_from_xinput_info, point, px,
 };
 
@@ -1479,14 +1477,19 @@ impl LinuxClient for X11Client {
         ))
     }
 
+    #[cfg(feature = "screen-capture")]
     fn is_screen_capture_supported(&self) -> bool {
         true
     }
 
+    #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
-    ) -> oneshot::Receiver<anyhow::Result<Vec<Box<dyn ScreenCaptureSource>>>> {
-        scap_screen_sources(&self.0.borrow().common.foreground_executor)
+    ) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Box<dyn crate::ScreenCaptureSource>>>>
+    {
+        crate::platform::scap_screen_capture::scap_screen_sources(
+            &self.0.borrow().common.foreground_executor,
+        )
     }
 
     fn open_window(

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

@@ -5,6 +5,8 @@ mod display;
 mod display_link;
 mod events;
 mod keyboard;
+
+#[cfg(feature = "screen-capture")]
 mod screen_capture;
 
 #[cfg(not(feature = "macos-blade"))]

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

@@ -2,14 +2,14 @@ use super::{
     BoolExt, MacKeyboardLayout,
     attributed_string::{NSAttributedString, NSMutableAttributedString},
     events::key_to_native,
-    is_macos_version_at_least, renderer, screen_capture,
+    renderer,
 };
 use crate::{
     Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString,
     CursorStyle, ForegroundExecutor, Image, ImageFormat, KeyContext, Keymap, MacDispatcher,
     MacDisplay, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay,
-    PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result, ScreenCaptureSource,
-    SemanticVersion, Task, WindowAppearance, WindowParams, hash,
+    PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task,
+    WindowAppearance, WindowParams, hash,
 };
 use anyhow::{Context as _, anyhow};
 use block::ConcreteBlock;
@@ -22,8 +22,8 @@ use cocoa::{
     },
     base::{BOOL, NO, YES, id, nil, selector},
     foundation::{
-        NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSOperatingSystemVersion,
-        NSProcessInfo, NSRange, NSString, NSUInteger, NSURL,
+        NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSRange, NSString,
+        NSUInteger, NSURL,
     },
 };
 use core_foundation::{
@@ -572,15 +572,17 @@ impl Platform for MacPlatform {
             .collect()
     }
 
+    #[cfg(feature = "screen-capture")]
     fn is_screen_capture_supported(&self) -> bool {
-        let min_version = NSOperatingSystemVersion::new(12, 3, 0);
-        is_macos_version_at_least(min_version)
+        let min_version = cocoa::foundation::NSOperatingSystemVersion::new(12, 3, 0);
+        super::is_macos_version_at_least(min_version)
     }
 
+    #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
-    ) -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>> {
-        screen_capture::get_sources()
+    ) -> oneshot::Receiver<Result<Vec<Box<dyn crate::ScreenCaptureSource>>>> {
+        super::screen_capture::get_sources()
     }
 
     fn active_window(&self) -> Option<AnyWindowHandle> {

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

@@ -263,10 +263,12 @@ impl Platform for TestPlatform {
         Some(self.active_display.clone())
     }
 
+    #[cfg(feature = "screen-capture")]
     fn is_screen_capture_supported(&self) -> bool {
         true
     }
 
+    #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
     ) -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>> {

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

@@ -432,10 +432,12 @@ impl Platform for WindowsPlatform {
         WindowsDisplay::primary_monitor().map(|display| Rc::new(display) as Rc<dyn PlatformDisplay>)
     }
 
+    #[cfg(feature = "screen-capture")]
     fn is_screen_capture_supported(&self) -> bool {
         false
     }
 
+    #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
     ) -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>> {