Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:08:00.345Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

linux.rs

204 lines · 7.0 KB · rust
1use anyhow::Result;
2use futures::StreamExt as _;
3use futures::channel::oneshot;
4use gpui::{AsyncApp, ScreenCaptureStream};
5use livekit::track;
6use livekit::webrtc::{
7    prelude::NV12Buffer,
8    video_frame::{VideoFrame, VideoRotation},
9    video_source::{RtcVideoSource, VideoResolution, native::NativeVideoSource},
10};
11use std::sync::{
12    Arc,
13    atomic::{AtomicBool, AtomicU64, Ordering},
14};
15
16static NEXT_WAYLAND_SHARE_ID: AtomicU64 = AtomicU64::new(1);
17const PIPEWIRE_TIMEOUT_S: u64 = 30;
18
19pub struct WaylandScreenCaptureStream {
20    id: u64,
21    stop_flag: Arc<AtomicBool>,
22    _capture_task: gpui::Task<()>,
23}
24
25impl WaylandScreenCaptureStream {
26    pub fn new(stop_flag: Arc<AtomicBool>, capture_task: gpui::Task<()>) -> Self {
27        Self {
28            id: NEXT_WAYLAND_SHARE_ID.fetch_add(1, Ordering::Relaxed),
29            stop_flag,
30            _capture_task: capture_task,
31        }
32    }
33}
34
35impl ScreenCaptureStream for WaylandScreenCaptureStream {
36    fn metadata(&self) -> Result<gpui::SourceMetadata> {
37        Ok(gpui::SourceMetadata {
38            id: self.id,
39            label: None,
40            is_main: None,
41            resolution: gpui::size(gpui::DevicePixels(1), gpui::DevicePixels(1)),
42        })
43    }
44}
45
46impl Drop for WaylandScreenCaptureStream {
47    fn drop(&mut self) {
48        self.stop_flag.store(true, Ordering::Release);
49    }
50}
51
52pub(crate) async fn start_wayland_desktop_capture(
53    cx: &mut AsyncApp,
54) -> Result<(
55    crate::LocalVideoTrack,
56    Arc<AtomicBool>,
57    gpui::Task<()>,
58    oneshot::Receiver<()>,
59)> {
60    use futures::channel::mpsc;
61    use gpui::FutureExt as _;
62    use libwebrtc::desktop_capturer::{
63        CaptureError, DesktopCaptureSourceType, DesktopCapturer, DesktopCapturerOptions,
64        DesktopFrame,
65    };
66    use libwebrtc::native::yuv_helper::argb_to_nv12;
67    use std::time::Duration;
68    use webrtc_sys::webrtc::ffi as webrtc_ffi;
69
70    fn webrtc_log_callback(message: String, severity: webrtc_ffi::LoggingSeverity) {
71        match severity {
72            webrtc_ffi::LoggingSeverity::Error => log::error!("[webrtc] {}", message.trim()),
73            _ => log::debug!("[webrtc] {}", message.trim()),
74        }
75    }
76
77    let _webrtc_log_sink = webrtc_ffi::new_log_sink(webrtc_log_callback);
78    log::debug!("Wayland desktop capture: WebRTC internal logging enabled");
79
80    let stop_flag = Arc::new(AtomicBool::new(false));
81    let (mut video_source_tx, mut video_source_rx) = mpsc::channel::<NativeVideoSource>(1);
82    let (failure_tx, failure_rx) = oneshot::channel::<()>();
83
84    let mut options = DesktopCapturerOptions::new(DesktopCaptureSourceType::Generic);
85    options.set_include_cursor(true);
86    let mut capturer = DesktopCapturer::new(options).ok_or_else(|| {
87        anyhow::anyhow!(
88            "Failed to create desktop capturer. \
89             Check that xdg-desktop-portal is installed and running."
90        )
91    })?;
92
93    let permanent_error = Arc::new(AtomicBool::new(false));
94    let stop_cb = stop_flag.clone();
95    let permanent_error_cb = permanent_error.clone();
96    capturer.start_capture(None, {
97        let mut video_source: Option<NativeVideoSource> = None;
98        let mut current_width: u32 = 0;
99        let mut current_height: u32 = 0;
100        let mut video_frame = VideoFrame {
101            rotation: VideoRotation::VideoRotation0,
102            buffer: NV12Buffer::new(1, 1),
103            timestamp_us: 0,
104        };
105
106        move |result: Result<DesktopFrame, CaptureError>| {
107            let frame = match result {
108                Ok(frame) => frame,
109                Err(CaptureError::Temporary) => return,
110                Err(CaptureError::Permanent) => {
111                    log::error!("Wayland desktop capture encountered a permanent error");
112                    permanent_error_cb.store(true, Ordering::Release);
113                    stop_cb.store(true, Ordering::Release);
114                    return;
115                }
116            };
117
118            let width = frame.width() as u32;
119            let height = frame.height() as u32;
120            if width != current_width || height != current_height {
121                current_width = width;
122                current_height = height;
123                video_frame.buffer = NV12Buffer::new(width, height);
124            }
125
126            let (stride_y, stride_uv) = video_frame.buffer.strides();
127            let (data_y, data_uv) = video_frame.buffer.data_mut();
128            argb_to_nv12(
129                frame.data(),
130                frame.stride(),
131                data_y,
132                stride_y,
133                data_uv,
134                stride_uv,
135                width as i32,
136                height as i32,
137            );
138
139            if let Some(source) = &video_source {
140                source.capture_frame(&video_frame);
141            } else {
142                let source = NativeVideoSource::new(VideoResolution { width, height }, true);
143                source.capture_frame(&video_frame);
144                video_source_tx.try_send(source.clone()).ok();
145                video_source = Some(source);
146            }
147        }
148    });
149
150    log::info!("Wayland desktop capture: starting capture loop");
151
152    let stop = stop_flag.clone();
153    let tokio_task = gpui_tokio::Tokio::spawn(cx, async move {
154        loop {
155            if stop.load(Ordering::Acquire) {
156                break;
157            }
158            capturer.capture_frame();
159            tokio::time::sleep(Duration::from_millis(33)).await;
160        }
161        drop(capturer);
162
163        if permanent_error.load(Ordering::Acquire) {
164            log::error!("Wayland screen capture ended due to a permanent capture error");
165            let _ = failure_tx.send(());
166        }
167    });
168
169    let capture_task = cx.background_executor().spawn(async move {
170        if let Err(error) = tokio_task.await {
171            log::error!("Wayland capture task failed: {error}");
172        }
173    });
174
175    let executor = cx.background_executor().clone();
176    let video_source = video_source_rx
177        .next()
178        .with_timeout(Duration::from_secs(PIPEWIRE_TIMEOUT_S), &executor)
179        .await
180        .map_err(|_| {
181            stop_flag.store(true, Ordering::Relaxed);
182            log::error!("Wayland desktop capture timed out.");
183            anyhow::anyhow!(
184                "Screen sharing timed out waiting for the first frame. \
185                 Check that xdg-desktop-portal and PipeWire are running, \
186                 and that your portal backend matches your compositor."
187            )
188        })?
189        .ok_or_else(|| {
190            stop_flag.store(true, Ordering::Relaxed);
191            anyhow::anyhow!(
192                "Screen sharing was canceled or the portal denied permission. \
193                 You can try again from the screen share button."
194            )
195        })?;
196
197    let track = super::LocalVideoTrack(track::LocalVideoTrack::create_video_track(
198        "screen share",
199        RtcVideoSource::Native(video_source),
200    ));
201
202    Ok((track, stop_flag, capture_task, failure_rx))
203}
204
Served at tenant.openagents/omega Member data and write actions are omitted.