gpui: Make screen capture dependency optional (#32937)

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>
This commit is contained in:
Hilmar Wiegand 2025-07-02 18:15:06 +02:00 committed by GitHub
parent 6d09f3fa40
commit 9dc3ac9657
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 73 additions and 33 deletions

View file

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

View file

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

View file

@ -25,6 +25,7 @@ mod test;
mod windows; mod windows;
#[cfg(all( #[cfg(all(
feature = "screen-capture",
any(target_os = "linux", target_os = "freebsd"), any(target_os = "linux", target_os = "freebsd"),
any(feature = "wayland", feature = "x11"), any(feature = "wayland", feature = "x11"),
))] ))]
@ -176,10 +177,28 @@ pub(crate) trait Platform: 'static {
None None
} }
#[cfg(feature = "screen-capture")]
fn is_screen_capture_supported(&self) -> bool; 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( fn screen_capture_sources(
&self, &self,
) -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>>; ) -> 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( fn open_window(
&self, &self,

View file

@ -23,7 +23,7 @@ pub(crate) use wayland::*;
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
pub(crate) use 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; 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 = (); pub(crate) type PlatformScreenCaptureFrame = ();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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